diff --git a/.jshintrc b/.jshintrc index 682e2b15..e4d3480f 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,6 +3,7 @@ "bitwise": true, "eqeqeq": true, "es3": true, + "freeze": true, "latedef": true, "noarg": true, "nonew": true, @@ -11,10 +12,11 @@ // Environment "browser": true, + "jquery": true, "globals": { "mw": false, - "jQuery": false, + "mediaWiki": false, "OO": false, "QUnit": false } diff --git a/HISTORY b/HISTORY index ee9118d2..e81769a2 100644 --- a/HISTORY +++ b/HISTORY @@ -1,3 +1,9 @@ +==RelatedArticles 2.1.0== +9932fee Add browser tests for ReadMore +8a73239 Update packages +9623c1c RelatedArticles load after half window scroll +ed338bb Disable RelatedArticles when editor enabled +a3d9b22 Fix loading related articles on small screens ==RelatedArticles 2.0.0== fbc6961 Fix RelatedPagesGateway unit test failures 18990e6 Correctly show related articles on desktop diff --git a/extension.json b/extension.json index f1f11684..76ca135b 100644 --- a/extension.json +++ b/extension.json @@ -1,6 +1,6 @@ { "name": "RelatedArticles", - "version": "2.0.0", + "version": "2.1.0", "author": [ "Roland Unger", "Hans Musil", @@ -88,7 +88,8 @@ ], "dependencies": [ "mediawiki.api", - "ext.relatedArticles.readMore.gateway" + "ext.relatedArticles.readMore.gateway", + "jquery.throttle-debounce" ], "targets": [ "mobile", diff --git a/package.json b/package.json index 3c970e67..43d3027d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "grunt-cli": "0.1.13", "grunt-contrib-jshint": "0.11.3", "grunt-banana-checker": "0.4.0", - "grunt-jscs": "2.1.0", - "grunt-jsonlint": "1.0.4" + "grunt-jscs": "2.4.0", + "grunt-jsonlint": "1.0.6" } } diff --git a/resources/ext.relatedArticles.readMore.bootstrap/index.js b/resources/ext.relatedArticles.readMore.bootstrap/index.js index 3cd056e3..208bb76d 100644 --- a/resources/ext.relatedArticles.readMore.bootstrap/index.js +++ b/resources/ext.relatedArticles.readMore.bootstrap/index.js @@ -1,4 +1,4 @@ -( function ( $ ) { +( function ( $, mw ) { var config = mw.config.get( [ 'skin', 'wgNamespaceNumber', 'wgMFMode', 'wgIsMainPage' ] ), relatedPages = new mw.relatedPages.RelatedPagesGateway( @@ -8,25 +8,46 @@ mw.config.get( 'wgRelatedArticlesUseCirrusSearch' ), mw.config.get( 'wgRelatedArticlesOnlyUseCirrusSearch' ) ), - LIMIT = 3; + LIMIT = 3, + debouncedLoad = $.debounce( 100, function () { + loadRelatedArticles(); + } ), + $window = $( window ); + function loadRelatedArticles() { + /** + * Threshold value to load related articles - after about half scroll + */ + var scrollThreshold = ( $( document ).height() / 2 ) - $window.height(); + + if ( $window.scrollTop() > scrollThreshold ) { + $.when( + // Note we load dependencies here rather than ResourceLoader + // to avoid PHP exceptions when Cards not installed + // which should never happen given the if statement. + mw.loader.using( [ 'ext.cards', 'ext.relatedArticles.readMore' ] ), + relatedPages.getForCurrentPage( LIMIT ) + ).done( function ( _, pages ) { + if ( pages.length ) { + mw.track( 'ext.relatedArticles.init', pages ); + } + } ); + // detach handler to stop subsequent loads on scroll + $window.off( 'scroll', debouncedLoad ); + } + } if ( config.wgNamespaceNumber === 0 && !config.wgIsMainPage && + // T120735 + mw.config.get( 'wgAction' ) === 'view' && // any skin except minerva stable ( config.skin !== 'minerva' || config.wgMFMode === 'beta' ) ) { - $.when( - // Note we load dependencies here rather than ResourceLoader - // to avoid PHP exceptions when Cards not installed - // which should never happen given the if statement. - mw.loader.using( [ 'ext.cards', 'ext.relatedArticles.readMore' ] ), - relatedPages.getForCurrentPage( LIMIT ) - ).done( function ( _, pages ) { - if ( pages.length ) { - mw.track( 'ext.relatedArticles.init', pages ); - } - } ); + // try related articles load on scroll + $window.on( 'scroll', debouncedLoad ); + // try an initial load, in case of no scroll + loadRelatedArticles(); } -}( jQuery ) ); +}( jQuery, mediaWiki ) ); diff --git a/resources/ext.relatedArticles.readMore.gateway/RelatedPagesGateway.js b/resources/ext.relatedArticles.readMore.gateway/RelatedPagesGateway.js index a99dea5d..7e9d19c3 100644 --- a/resources/ext.relatedArticles.readMore.gateway/RelatedPagesGateway.js +++ b/resources/ext.relatedArticles.readMore.gateway/RelatedPagesGateway.js @@ -1,4 +1,4 @@ -( function ( $ ) { +( function ( $, mw ) { // FIXME: Move into separate file as this module becomes larger. mw.relatedPages = {}; @@ -92,4 +92,4 @@ } mw.relatedPages.RelatedPagesGateway = RelatedPagesGateway; -}( jQuery ) ); +}( jQuery, mediaWiki ) ); diff --git a/resources/ext.relatedArticles.readMore/eventLogging.js b/resources/ext.relatedArticles.readMore/eventLogging.js index 58f4b11c..33a4c8e7 100644 --- a/resources/ext.relatedArticles.readMore/eventLogging.js +++ b/resources/ext.relatedArticles.readMore/eventLogging.js @@ -77,4 +77,4 @@ } ); } ); -} )( jQuery ); +}( jQuery, mediaWiki ) ); diff --git a/resources/ext.relatedArticles.readMore/index.js b/resources/ext.relatedArticles.readMore/index.js index 9d181cb5..bc83d6e2 100644 --- a/resources/ext.relatedArticles.readMore/index.js +++ b/resources/ext.relatedArticles.readMore/index.js @@ -1,4 +1,4 @@ -( function ( $ ) { +( function ( $, mw ) { // Make sure 'ext.cards' is loaded. It may not be because of the race // condition in the bootstrap file. mw.loader.using( 'ext.cards' ).done( function () { @@ -55,4 +55,4 @@ } ); } ); -}( jQuery ) ); +}( jQuery, mediaWiki ) ); diff --git a/resources/ext.relatedArticles.readMore/readMore.less b/resources/ext.relatedArticles.readMore/readMore.less index 001f4c77..d4dcde13 100644 --- a/resources/ext.relatedArticles.readMore/readMore.less +++ b/resources/ext.relatedArticles.readMore/readMore.less @@ -1,5 +1,12 @@ @import "mediawiki.ui/variables"; +// Hide RelatedArticles when VE is activated (https://phabricator.wikimedia.org/T120443) +.ve-activated { + .ra-read-more { + display: none; + } +} + .ra-read-more { margin-top: 35px; margin-bottom: 70px; diff --git a/tests/browser/LocalSettings.php b/tests/browser/LocalSettings.php new file mode 100644 index 00000000..9a2d8bc7 --- /dev/null +++ b/tests/browser/LocalSettings.php @@ -0,0 +1,7 @@ + # Linux/Unix/Mac + set MEDIAWIKI_USER= # Windows + + export MEDIAWIKI_PASSWORD= # Linux/Unix/Mac + set MEDIAWIKI_PASSWORD= # Windows + +In addition to this create another user which will be reserved for new uploads +"Selenium_newuser". The password for this user should be the same as +MEDIAWIKI_PASSWORD + +Tests that use the "Given I create a random page using the API" step need to set +the MEDIAWIKI_API_URL environment variable, e.g. +export MEDIAWIKI_API_URL=http://en.wikipedia.beta.wmflabs.org/w/api.php + +Run the tests from the RelatedArticles directory with: + + make cucumber + +If you want to run a single set of tests, go to the tests/browser directory and +call 'bundle exec cucumber' with the path to the test file. For example, to run +only the watchlist tests: + + bundle exec cucumber features/read_more_desktop.feature + +XML report (for Jenkins) is created at tests/browser/reports/junit. + +Jenkins is hosted at https://wmf.ci.cloudbees.com/ and it drives +browsers at http://saucelabs.com/ + +For more information about running Selenium tests please see +https://github.com/wikimedia/mediawiki-selenium diff --git a/tests/browser/environments.yml b/tests/browser/environments.yml new file mode 100644 index 00000000..cb14ed5a --- /dev/null +++ b/tests/browser/environments.yml @@ -0,0 +1,50 @@ +# Customize this configuration as necessary to provide defaults for various +# test environments. +# +# The set of defaults to use is determined by the MEDIAWIKI_ENVIRONMENT +# environment variable. +# +# export MEDIAWIKI_ENVIRONMENT=mw-vagrant-host +# bundle exec cucumber +# +# Additional variables set by the environment will override the corresponding +# defaults defined here. +# +# export MEDIAWIKI_ENVIRONMENT=mw-vagrant-host +# export MEDIAWIKI_USER=Selenium_user2 +# bundle exec cucumber +# +mw-vagrant-host: &default + mediawiki_url: http://127.0.0.1:8080/wiki/ + user_factory: true + +barry: + browser: phantomjs + user_factory: false + # mediawiki_url: Will be set manually + +mw-vagrant-guest: + mediawiki_url: http://127.0.0.1/wiki/ + user_factory: true + +beta: + mediawiki_url: http://en.m.wikipedia.beta.wmflabs.org/wiki/ + mediawiki_user: Selenium_user + # mediawiki_password: SET THIS IN THE ENVIRONMENT! + +staging: + mediawiki_url: http://reading-web-staging.wmflabs.org/wiki/ + mediawiki_user: Selenium_user + # mediawiki_password: SET THIS IN THE ENVIRONMENT! + +test2: + mediawiki_url: http://test2.wikipedia.org/wiki/ + mediawiki_user: Selenium_user + # mediawiki_password: SET THIS IN THE ENVIRONMENT! + +integration: + browser: chrome + user_factory: true + # mediawiki_url: JENKINS WILL SET THIS + +default: *default diff --git a/tests/browser/features/read_more.feature b/tests/browser/features/read_more.feature new file mode 100644 index 00000000..d9cf5e52 --- /dev/null +++ b/tests/browser/features/read_more.feature @@ -0,0 +1,30 @@ +@staging @integration +Feature: ReadMore + Background: + Given RelatedArticles test pages are installed + + Scenario: ReadMore is not present in minerva stable + Given I am using the mobile site + And I am on the "Related Articles 1" page + And page has fully loaded without ReadMore code + Then I must not see ReadMore + + Scenario: ReadMore is present in minerva beta + Given I am using the mobile site + And I am in mobile beta mode + And I am on the "Related Articles 1" page + And page has fully loaded with ReadMore code + Then I must see ReadMore + And ReadMore must have three cards + + Scenario: ReadMore is not present when disabled as a BetaFeature + Given I am on the "Related Articles 1" page + And page has fully loaded without ReadMore code + Then I must not see ReadMore + + Scenario: ReadMore is present when enabled as a BetaFeature + Given I am logged into the website + And ReadMore is enabled as a beta feature + And I am on the "Related Articles 1" page + And page has fully loaded with ReadMore code + Then I must see ReadMore diff --git a/tests/browser/features/support/env.rb b/tests/browser/features/support/env.rb new file mode 100644 index 00000000..5eff4ce5 --- /dev/null +++ b/tests/browser/features/support/env.rb @@ -0,0 +1,4 @@ +require 'mediawiki_selenium' + +require 'mediawiki_selenium/support' +require 'mediawiki_selenium/step_definitions' diff --git a/tests/browser/features/support/hooks.rb b/tests/browser/features/support/hooks.rb new file mode 100644 index 00000000..56f159bc --- /dev/null +++ b/tests/browser/features/support/hooks.rb @@ -0,0 +1,6 @@ +# Needed for cucumber --dry-run -f stepdefs +require_relative 'env' + +Before('@skip') do |scenario| + scenario.skip_invoke! +end diff --git a/tests/browser/features/support/pages/article_page.rb b/tests/browser/features/support/pages/article_page.rb new file mode 100644 index 00000000..d1d547cf --- /dev/null +++ b/tests/browser/features/support/pages/article_page.rb @@ -0,0 +1,11 @@ +# Standard article page +class ArticlePage + include PageObject + + page_url '<%= URI.encode(params[:article_name]) %>'\ + '<%= URI.encode(params[:query_string]) if params[:query_string] %>'\ + '<%= params[:hash] %>' + + aside(:read_more, css: '.ra-read-more') + li(:read_more_cards, css: '.ext-cards-card') +end diff --git a/tests/browser/features/support/pages/login_page.rb b/tests/browser/features/support/pages/login_page.rb new file mode 100644 index 00000000..de791ccf --- /dev/null +++ b/tests/browser/features/support/pages/login_page.rb @@ -0,0 +1,15 @@ +class LoginPage + include PageObject + page_url 'Special:Userlogin' + + text_field(:username, name: 'wpName') + text_field(:password, name: 'wpPassword') + + def login_with(username, password) + # deal with autocomplete + self.username_element.when_present.clear + self.username = username + self.password = password + login + end +end diff --git a/tests/browser/features/support/pages/main_page.rb b/tests/browser/features/support/pages/main_page.rb new file mode 100644 index 00000000..66685bd0 --- /dev/null +++ b/tests/browser/features/support/pages/main_page.rb @@ -0,0 +1,5 @@ +class MainPage + include PageObject + + page_url 'Main_Page' +end diff --git a/tests/browser/features/support/pages/special_preferences_page.rb b/tests/browser/features/support/pages/special_preferences_page.rb new file mode 100644 index 00000000..0bf0c971 --- /dev/null +++ b/tests/browser/features/support/pages/special_preferences_page.rb @@ -0,0 +1,14 @@ +class SpecialPreferencesPage + include PageObject + page_url 'Special:Preferences' + + a(:beta_features_tab, css: '#preftab-betafeatures') + text_field(:read_more_checkbox, css: '#mw-input-wpread-more') + button(:submit_button, css: '#prefcontrol') + + def enable_read_more + self.beta_features_tab_element.when_present.click + read_more_checkbox_element.when_present.click if read_more_checkbox_element.attribute('checked').nil? + submit_button_element.when_present.click + end +end diff --git a/tests/browser/features/support/permissions.sqlite b/tests/browser/features/support/permissions.sqlite new file mode 100644 index 00000000..06d20318 Binary files /dev/null and b/tests/browser/features/support/permissions.sqlite differ diff --git a/tests/browser/features/support/step_definitions/common_steps.rb b/tests/browser/features/support/step_definitions/common_steps.rb new file mode 100644 index 00000000..3d639af1 --- /dev/null +++ b/tests/browser/features/support/step_definitions/common_steps.rb @@ -0,0 +1,83 @@ +Given(/^RelatedArticles test pages are installed$/) do + api.create_page 'Related Articles 1', + File.read('samples/related_articles_1.wikitext') + + api.create_page 'Related Articles 2', + File.read('samples/related_articles_2.wikitext') + + api.create_page 'Related Articles 3', + File.read('samples/related_articles_3.wikitext') + + api.create_page 'Related Articles 4', + File.read('samples/related_articles_4.wikitext') +end + +Given(/^I am using the mobile site$/) do + visit(MainPage) do |page| + page_uri = URI.parse(page.page_url_value) + + domain = page_uri.host == 'localhost' ? nil : page_uri.host + browser.cookies.add 'mf_useformat', 'true', domain: domain + + page.refresh + end +end + +Given /^I am in mobile beta mode$/ do + visit(MainPage) do |page| + page_uri = URI.parse(page.page_url_value) + + # A domain is explicitly given to avoid a bug in earlier versions of Chrome + domain = page_uri.host == 'localhost' ? nil : page_uri.host + browser.cookies.add 'mf_useformat', 'true', domain: domain + browser.cookies.add 'optin', 'beta', domain: domain + + page.refresh + end +end + +Given(/^I am logged into the website$/) do + visit(LoginPage).login_with(user, password) +end + +Given(/^I am on the "(.*?)" page/) do |arg1| + visit(ArticlePage, using_params: { article_name: arg1 }) +end + +Then(/^page has fully loaded with ReadMore code$/) do + on(ArticlePage) do |page| + page.wait_until do + # Wait for async JS to hijack standard link + script = 'return mw && mw.loader && '\ + 'mw.loader.getState("ext.relatedArticles.readMore") === "ready";' + page.execute_script(script) + end + end +end + +Then(/^page has fully loaded without ReadMore code$/) do + on(ArticlePage) do |page| + page.wait_until do + # Wait for async JS to hijack standard link + script = 'return mw && mw.loader && '\ + 'mw.loader.getState("ext.relatedArticles.readMore") === "registered";' + page.execute_script(script) + end + end +end + +Then(/^ReadMore is enabled as a beta feature$/) do + visit(SpecialPreferencesPage).enable_read_more() +end + +Then(/^I must see ReadMore$/) do + expect(on(ArticlePage).read_more_element.when_present).to be_visible +end + +Then(/^I must not see ReadMore$/) do + expect(on(ArticlePage).read_more_element).to_not be_visible +end + +Then(/^ReadMore must have three cards$/) do + expect(browser.execute_script("return $('.ext-cards-card').length")).to eq(3) +end diff --git a/tests/browser/samples/related_articles_1.wikitext b/tests/browser/samples/related_articles_1.wikitext new file mode 100644 index 00000000..7071bda7 --- /dev/null +++ b/tests/browser/samples/related_articles_1.wikitext @@ -0,0 +1,5 @@ +RelatedArticles page 1 + +{{#related:related_articles_2}} +{{#related:related_articles_3}} +{{#related:related_articles_4}} diff --git a/tests/browser/samples/related_articles_2.wikitext b/tests/browser/samples/related_articles_2.wikitext new file mode 100644 index 00000000..63cbd033 --- /dev/null +++ b/tests/browser/samples/related_articles_2.wikitext @@ -0,0 +1,3 @@ +RelatedArticles page 2 + +{{#related:related_articles_1}} diff --git a/tests/browser/samples/related_articles_3.wikitext b/tests/browser/samples/related_articles_3.wikitext new file mode 100644 index 00000000..62d5de5d --- /dev/null +++ b/tests/browser/samples/related_articles_3.wikitext @@ -0,0 +1,3 @@ +RelatedArticles page 3 + +{{#related:related_articles_4}} diff --git a/tests/browser/samples/related_articles_4.wikitext b/tests/browser/samples/related_articles_4.wikitext new file mode 100644 index 00000000..7dbea90c --- /dev/null +++ b/tests/browser/samples/related_articles_4.wikitext @@ -0,0 +1,3 @@ +RelatedArticles page 4 + +{{#related:related_articles_1}}