From 14df699cef226ab1a9dcc8ea91a6a2757b8a9fd5 Mon Sep 17 00:00:00 2001 From: Doug Beck Date: Sun, 30 Jul 2017 11:40:15 -0400 Subject: [PATCH 1/4] Cleanup readme --- README.md | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 6dc1ef9..8b51ed2 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,7 @@ -classList.js is a cross-browser JavaScript shim that fully implements `element.classList`. Refer to [the MDN page on `element.classList`][1] for more information. +# classList.js -This works in every browser except IE 7 or earlier. +Cross-browser JavaScript shim that fully implements `element.classList`. -classList.js is available on these public CDN URLs, allowing you to use this already small file at nearly zero size overhead. +Refer to [the MDN page on `element.classList`][mdn] for more information. - - - - - -If you would like other versions (such as the current one) hosted there, follow the instructions at -https://github.com/jsdelivr/jsdelivr -and -https://github.com/cdnjs/cdnjs -to prepare a pull request. - -![Tracking image](https://in.getclicky.com/212712ns.gif) - - [1]: https://developer.mozilla.org/en/DOM/element.classList "MDN / DOM / element.classList" +[mdn]: https://developer.mozilla.org/en/DOM/element.classList From 4d1ab878a024a4a9ec27bd08ac396e03bfcc3edb Mon Sep 17 00:00:00 2001 From: Doug Beck Date: Sun, 30 Jul 2017 01:07:09 -0400 Subject: [PATCH 2/4] Add test scaffolding Using webdriver.io. Also tried karma, grunt plugins, and nightwatch.js. All required way too much scaffolding. E2E testing is a pain because you need three processes: * http server * web driver server (or a proxy server for cloud browsers) * test runner `wdio.conf.js` makes this easy to bake into one call. Misc thoughts: * webdriver.io API is synchronous, making it significantly easier to write clean tests. * Mocha formatted tests seem to "just work" out of the box (not so with nightwatch) * The config is easy to set up (and includes a wizard) * Nightwatch made it hard/impossible to skip tests --- .gitignore | 6 +++++ .npmignore | 6 +++++ README.md | 49 +++++++++++++++++++++++++++++++++++++++-- package.json | 30 ++++++++++++++++++++++--- test/specs/add.html | 25 +++++++++++++++++++++ test/specs/add.js | 16 ++++++++++++++ test/wdio.defaults.js | 47 +++++++++++++++++++++++++++++++++++++++ test/wdio.sample.js | 15 +++++++++++++ test/wdio.sauce.js | 28 +++++++++++++++++++++++ test/wdio.standalone.js | 16 ++++++++++++++ 10 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 test/specs/add.html create mode 100644 test/specs/add.js create mode 100644 test/wdio.defaults.js create mode 100644 test/wdio.sample.js create mode 100644 test/wdio.sauce.js create mode 100644 test/wdio.standalone.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a869e12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/user.json +/wdio.*.js +classlist.js-*.tgz +node_modules/ +package-lock.json +package/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..d48f2d7 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +.travis.yml +classlist.js-*.tgz +package/ +test/ +user.json +wdio.*.js diff --git a/README.md b/README.md index 8b51ed2..6dbeb30 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,51 @@ Cross-browser JavaScript shim that fully implements `element.classList`. -Refer to [the MDN page on `element.classList`][mdn] for more information. +Refer to [the MDN page on `element.classList`][MDN] for more information. -[mdn]: https://developer.mozilla.org/en/DOM/element.classList +[MDN]: https://developer.mozilla.org/en/DOM/element.classList + + +## Development + +### Getting started + +``` +npm install +``` + +### Run the tests using a local web driver + +``` +npm test +``` + +### Run the tests using cloud browsers + +Requires a [Sauce Labs][] username and access key. + +``` +echo '{"user": "", "key": "" }' > user.json +npm test:ci +``` + +[Sauce Labs]: https://saucelabs.com/ + +### Run the tests using custom configuration + +The tests use [webdriver.io][]. + +Create a `wdio.conf.js`: + +``` +wdio config +# or extend an existing config +cp test/wdio.sample.js wdio.conf.js +``` + +Run the tests: +``` +wdio +``` + +[webdriver.io]: http://webdriver.io/ diff --git a/package.json b/package.json index c7ab5df..f1eab92 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,11 @@ "description": "Cross-browser JavaScript shim that fully implements element.classList (referenced on MDN)", "main": "classList.js", "directories": { - "test": "tests" + "test": "wdio test/wdio.local.js" }, "scripts": { - "test": "bash ./script/test" + "test": "wdio test/wdio.standalone.js", + "test:ci": "wdio test/wdio.sauce.js" }, "repository": { "type": "git", @@ -20,9 +21,32 @@ "cross-browser" ], "author": "me@eligrey.com", + "contributors": [ + "Eli Grey ", + "Doug Beck " + ], "license": "Unlicense", "bugs": { "url": "https://github.com/eligrey/classList.js/issues" }, - "homepage": "https://github.com/eligrey/classList.js#readme" + "homepage": "https://github.com/eligrey/classList.js#readme", + "jshintConfig": { + "esnext": true, + "node": true, + "mocha": true, + "globals": { + "agent": true, + "browser": true, + "expect": true + } + }, + "devDependencies": { + "chai": "^4.1.0", + "wdio-mocha-framework": "^0.5.10", + "wdio-sauce-service": "^0.4.0", + "wdio-selenium-standalone-service": "0.0.9", + "wdio-spec-reporter": "^0.1.0", + "wdio-static-server-service": "^1.0.1", + "webdriverio": "^4.8.0" + } } diff --git a/test/specs/add.html b/test/specs/add.html new file mode 100644 index 0000000..c1f6cbd --- /dev/null +++ b/test/specs/add.html @@ -0,0 +1,25 @@ + + + + + classList.add + + + +

classList.add

+ +
+ + +
+ + + + + diff --git a/test/specs/add.js b/test/specs/add.js new file mode 100644 index 0000000..a339e37 --- /dev/null +++ b/test/specs/add.js @@ -0,0 +1,16 @@ +'use strict'; + +describe('.classList.add', function() { + + before(function() { + browser.url('test/specs/add.html'); + if(browser.getAttribute('#native', 'class') === 'supported') { + browser.logger.info(`${agent} skipping .add`); + this.skip(); + } + }); + + it('adds class "all-good"', function() { + expect(browser.getAttribute('#polyfilled', 'class')).to.equal('all-good'); + }); +}); diff --git a/test/wdio.defaults.js b/test/wdio.defaults.js new file mode 100644 index 0000000..5927e62 --- /dev/null +++ b/test/wdio.defaults.js @@ -0,0 +1,47 @@ +const path = require('path'); +const chai = require('chai'); + +exports.getSauceCredentials = function() { + + let creds; + + try { + creds = require(path.join(__dirname, '..', 'user.json')); + } catch (e) { + creds = { + user: process.env.SAUCE_USERNAME, + key: process.env.SAUCE_ACCESS_KEY, + }; + } + + console.assert(creds.user, "Missing Sauce Labs username"); + console.assert(creds.key, "Missing Sauce Labs access key"); + + return creds; +}; + +exports.config = { + + before: function (capabilities, specs) { + global.expect = chai.expect; + const session = browser.session().value; + global.agent = `${session.browserName} ${session.browserVersion}`; + }, + + baseUrl: 'http://localhost:4567', + + logLevel: 'error', + + framework: 'mocha', + + reporters: ['spec'], + + specs: [ + path.join(__dirname, 'specs/**/*.js') + ], + + staticServerFolders: [ + { mount: '/', path: path.join(__dirname, '..') } + ], + +}; diff --git a/test/wdio.sample.js b/test/wdio.sample.js new file mode 100644 index 0000000..a4e4d1a --- /dev/null +++ b/test/wdio.sample.js @@ -0,0 +1,15 @@ +// can use any of the configs found in `/test` +const config = require('./test/wdio.defaults').config; + +exports.config = Object.assign(config, { + + capabilities: [ + { browserName: 'firefox', }, + ], + + services: [ + 'static-server', + 'selenium-standalone', + ], + +}); diff --git a/test/wdio.sauce.js b/test/wdio.sauce.js new file mode 100644 index 0000000..7300490 --- /dev/null +++ b/test/wdio.sauce.js @@ -0,0 +1,28 @@ +const path = require('path'); + +let defaults = require(path.join(__dirname, 'wdio.defaults')); + +const creds = defaults.getSauceCredentials(); + +exports.config = Object.assign(defaults.config, { + + // If a browser skips all tests then .classList is fully supported and + // it can be removed from this list. + // https://saucelabs.com/platforms + capabilities: [ + { browserName: 'internet explorer', version: 8, }, + { browserName: 'internet explorer', version: 9, }, + { browserName: 'internet explorer', version: 10, }, + { browserName: 'internet explorer', version: 11, }, + ], + + sauceConnect: true, + user: creds.user, + key: creds.key, + + services: [ + 'static-server', + 'sauce', + ], + +}); diff --git a/test/wdio.standalone.js b/test/wdio.standalone.js new file mode 100644 index 0000000..fd277ff --- /dev/null +++ b/test/wdio.standalone.js @@ -0,0 +1,16 @@ +const path = require('path'); + +let defaults = require(path.join(__dirname, 'wdio.defaults')); + +exports.config = Object.assign(defaults.config, { + + capabilities: [ + { browserName: 'firefox', }, + ], + + services: [ + 'static-server', + 'selenium-standalone', + ], + +}); From 6efba13faedf062e2d6c192686ddb2f35388b5c9 Mon Sep 17 00:00:00 2001 From: Doug Beck Date: Sun, 30 Jul 2017 12:17:01 -0400 Subject: [PATCH 3/4] Rewrite all the tests --- script/test | 23 ------------ test/specs/add.html | 12 ++++--- test/specs/add.js | 15 ++++++-- test/specs/remove.html | 33 +++++++++++++++++ test/specs/remove.js | 30 ++++++++++++++++ test/specs/svg.html | 41 +++++++++++++++++++++ test/specs/svg.js | 18 ++++++++++ test/specs/toggle.html | 59 ++++++++++++++++++++++++++++++ test/specs/toggle.js | 81 +++++++++++++++++++++++++++++++++++++++++ tests/qunit.html | 16 --------- tests/remove.js | 10 ------ tests/runner.coffee | 46 ------------------------ tests/tests.js | 82 ------------------------------------------ 13 files changed, 283 insertions(+), 183 deletions(-) delete mode 100755 script/test create mode 100644 test/specs/remove.html create mode 100644 test/specs/remove.js create mode 100644 test/specs/svg.html create mode 100644 test/specs/svg.js create mode 100644 test/specs/toggle.html create mode 100644 test/specs/toggle.js delete mode 100644 tests/qunit.html delete mode 100644 tests/remove.js delete mode 100644 tests/runner.coffee delete mode 100644 tests/tests.js diff --git a/script/test b/script/test deleted file mode 100755 index e3508e8..0000000 --- a/script/test +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -set -e - -if [ -t 1 ]; then - red="$(printf "\033[31m")" - brightred="$(printf "\033[31;1m")" - green="$(printf "\033[32m")" - reset="$(printf "\033[m")" -else - red= - brightred= - green= - reset= -fi - -phantomjs tests/runner.coffee tests/qunit.html | sed -E " - # failure line: - s/^(✘.+)/${red}\\1${reset}/ - # failure details: - s/^( .+)/${brightred}\\1${reset}/ - # success marker: - s/(✔︎)/${green}\\1${reset}/ -" diff --git a/test/specs/add.html b/test/specs/add.html index c1f6cbd..a45e179 100644 --- a/test/specs/add.html +++ b/test/specs/add.html @@ -8,17 +8,21 @@

classList.add

-
+
+
-
+
+
diff --git a/test/specs/add.js b/test/specs/add.js index a339e37..220d081 100644 --- a/test/specs/add.js +++ b/test/specs/add.js @@ -4,13 +4,24 @@ describe('.classList.add', function() { before(function() { browser.url('test/specs/add.html'); - if(browser.getAttribute('#native', 'class') === 'supported') { + const ab = browser.getAttribute('#native-ab', 'class'); + if( + browser.getAttribute('#native-simple', 'class') === 'supported' && + ab.includes('a') && ab.includes('b') + ) { browser.logger.info(`${agent} skipping .add`); this.skip(); } }); it('adds class "all-good"', function() { - expect(browser.getAttribute('#polyfilled', 'class')).to.equal('all-good'); + const classStr = browser.getAttribute('#polyfilled-simple', 'class'); + expect(classStr).to.equal('all-good'); + }); + + it('adds classes "a" and "b"', function() { + const ab = browser.getAttribute('#polyfilled-ab', 'class'); + expect(ab).to.include('a'); + expect(ab).to.include('b'); }); }); diff --git a/test/specs/remove.html b/test/specs/remove.html new file mode 100644 index 0000000..e7590d0 --- /dev/null +++ b/test/specs/remove.html @@ -0,0 +1,33 @@ + + + + + classList.remove + + + +

classList.remove

+ +
+
+
+ + +
+
+
+ + + + + diff --git a/test/specs/remove.js b/test/specs/remove.js new file mode 100644 index 0000000..db3bade --- /dev/null +++ b/test/specs/remove.js @@ -0,0 +1,30 @@ +'use strict'; + +describe('.classList.remove', function() { + + before(function() { + browser.url('test/specs/remove.html'); + if( + browser.getAttribute('#native-simple', 'class') === 'hi' && + browser.getAttribute('#native-repeat', 'class') === '' && + browser.getAttribute('#native-list', 'class').includes('yo') == false + ) { + browser.logger.info(`${agent} skipping .remove`); + this.skip(); + } + }); + + it('removes "yo"', function() { + expect(browser.getAttribute('#polyfilled-simple', 'class')).to.equal('hi'); + }); + + it('removes all instances of "yo"', function() { + expect(browser.getAttribute('#polyfilled-repeat', 'class')).to.equal(''); + }); + + it('removes "yo" from a list of classes', function() { + const classStr = browser.getAttribute('#polyfilled-list', 'class'); + expect(classStr).to.not.include('yo'); + }); + +}); diff --git a/test/specs/svg.html b/test/specs/svg.html new file mode 100644 index 0000000..e889b63 --- /dev/null +++ b/test/specs/svg.html @@ -0,0 +1,41 @@ + + + + + svgElement.classList + + + + +

svgElement.classList

+ + + + + + + + + + + + + + + + diff --git a/test/specs/svg.js b/test/specs/svg.js new file mode 100644 index 0000000..3cffc8c --- /dev/null +++ b/test/specs/svg.js @@ -0,0 +1,18 @@ +'use strict'; + +describe('svgElement.classList', function() { + + before(function() { + browser.url('test/specs/svg.html'); + if(browser.getAttribute('#native', 'class') == 'green') { + browser.logger.info(`${agent} skipping svg .classList`); + this.skip(); + } + }); + + it('has "red" removed and "green" toggled on', function() { + const classStr = browser.getAttribute('#polyfilled', 'class'); + expect(classStr).to.equal('green'); + }); + +}); diff --git a/test/specs/toggle.html b/test/specs/toggle.html new file mode 100644 index 0000000..c0e7117 --- /dev/null +++ b/test/specs/toggle.html @@ -0,0 +1,59 @@ + + + + + classList.toggle + + + +

classList.remove

+ + +
+
+
+
+ + + +
+
+
+
+ + + + + + diff --git a/test/specs/toggle.js b/test/specs/toggle.js new file mode 100644 index 0000000..112a0c2 --- /dev/null +++ b/test/specs/toggle.js @@ -0,0 +1,81 @@ +'use strict'; + +describe('.classList.toggle', function() { + + before(function() { + browser.url('test/specs/toggle.html'); + if( + browser.getAttribute('#native-on', 'class') === 'yo' && + browser.getAttribute('#native-off', 'class') === '' && + browser.getAttribute('#native-force-on', 'class') == 'yo' && + browser.getAttribute('#native-force-off', 'class') == '' + ) { + browser.logger.info(`${agent} skipping .toggle`); + this.skip(); + } + }); + + it('adds class "yo"', function() { + const classStr = browser.getAttribute('#polyfilled-on', 'class'); + expect(classStr).to.equal('yo'); + }); + + it('removes class "yo"', function() { + const classStr = browser.getAttribute('#polyfilled-off', 'class'); + expect(classStr).to.equal(''); + }); + + it('retains class "yo" when force is `true`', function() { + const classStr = browser.getAttribute('#polyfilled-force-on', 'class'); + expect(classStr).to.equal('yo'); + }); + + it('does not add a class when force is `false`', function() { + const classStr = browser.getAttribute('#polyfilled-force-off', 'class'); + expect(classStr).to.equal(''); + }); + +}); + + +describe('.classList.toggle return', function() { + + before(function() { + browser.url('test/specs/toggle.html'); + const toggleOn = browser.execute('return window.nativeToggleOn').value; + const toggleOff = browser.execute('return window.nativeToggleOff').value; + const toggleForceOn = browser.execute('return window.nativeToggleForceOn').value; + const toggleForceOff = browser.execute('return window.nativeToggleForceOff').value; + + if( + toggleOn === true && + toggleOff === false && + toggleForceOn === true && + toggleForceOff === false + ) { + browser.logger.info(`${agent} skipping .toggle return`); + this.skip(); + } + }); + + it('is `true` when class is toggled on', function() { + const returnVal = browser.execute('return window.polyfilledToggleOn').value; + expect(returnVal).to.equal(true); + }); + + it('is `false` when class is toggled off', function() { + const returnVal = browser.execute('return window.polyfilledToggleOff').value; + expect(returnVal).to.equal(false); + }); + + it('is `true` when force is `true`', function() { + const returnVal = browser.execute('return window.polyfilledToggleForceOn').value; + expect(returnVal).to.equal(true); + }); + + it('is `false` when force is `false`', function() { + const returnVal = browser.execute('return window.polyfilledToggleForceOff').value; + expect(returnVal).to.equal(false); + }); + +}); diff --git a/tests/qunit.html b/tests/qunit.html deleted file mode 100644 index d80d3b4..0000000 --- a/tests/qunit.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - QUnit Tests - - - -
-
- - - - - - diff --git a/tests/remove.js b/tests/remove.js deleted file mode 100644 index 24cda33..0000000 --- a/tests/remove.js +++ /dev/null @@ -1,10 +0,0 @@ -QUnit.module("classList.remove"); - -QUnit.test("Removes duplicated instances of class", function(assert) { - var el = document.createElement("p"), cList = el.classList; - el.className = "ho ho ho" - - cList.remove("ho"); - assert.ok(!cList.contains("ho"), "Should remove all instances of 'ho'"); - assert.strictEqual(el.className, "") -}); diff --git a/tests/runner.coffee b/tests/runner.coffee deleted file mode 100644 index be9c56a..0000000 --- a/tests/runner.coffee +++ /dev/null @@ -1,46 +0,0 @@ -urls = require('system').args.slice(1) -page = require('webpage').create() -timeout = 3000 - -qunitHooks = -> - window.document.addEventListener 'DOMContentLoaded', -> - for callback in ['log', 'testDone', 'done'] - do (callback) -> - QUnit[callback] (result) -> - window.callPhantom - name: "QUnit.#{callback}" - data: result - -page.onInitialized = -> page.evaluate qunitHooks - -page.onConsoleMessage = (msg) -> console.log msg - -page.onCallback = (event) -> - if event.name is 'QUnit.log' - details = event.data - if details.result is false - console.log "✘ #{details.module}: #{details.name}" - if details.message and details.message isnt "failed" - console.log " #{details.message}" - if "actual" of details - console.log " expected: #{details.expected}" - console.log " actual: #{details.actual}" - else if event.name is 'QUnit.testDone' - result = event.data - unless result.failed - console.log "✔︎ #{result.module}: #{result.name}" - else if event.name is 'QUnit.done' - res = event.data - console.log "#{res.total} tests, #{res.failed} failed. Done in #{res.runtime} ms" - phantom.exit if !res.total or res.failed then 1 else 0 - -for url in urls - page.open url, (status) -> - if status isnt 'success' - console.error "failed opening #{url}: #{status}" - phantom.exit 1 - else - setTimeout -> - console.error "ERROR: Test execution has timed out" - phantom.exit 1 - , timeout diff --git a/tests/tests.js b/tests/tests.js deleted file mode 100644 index b76a72a..0000000 --- a/tests/tests.js +++ /dev/null @@ -1,82 +0,0 @@ -QUnit.module("classList.toggle"); - -QUnit.test("Adds a class", function(assert) { - var cList = document.createElement("p").classList; - - cList.toggle("c1"); - assert.ok(cList.contains("c1"), "Adds a class that is not present"); - - assert.strictEqual( - cList.toggle("c2"), - true, - "Returns true when class is added" - ); -}); - -QUnit.test("Removes a class", function(assert) { - var cList = document.createElement("p").classList; - - cList.add("c1"); - cList.toggle("c1"); - assert.ok(!cList.contains("c1"), "Removes a class that is present"); - - cList.add("c2"); - assert.strictEqual( - cList.toggle("c2"), - false, - "Return false when class is removed" - ); -}); - -QUnit.test("Adds class with second argument", function(assert) { - var cList = document.createElement("p").classList; - - cList.toggle("c1", true); - assert.ok(cList.contains("c1"), "Adds a class"); - - assert.strictEqual( - cList.toggle("c2", true), - true, - "Returns true when class is added" - ); - - cList.add("c3"); - cList.toggle("c3", true); - assert.ok( - cList.contains("c3"), - "Does not remove a class that is already present" - ); - - cList.add("c4"); - assert.strictEqual( - cList.toggle("c4", true), - true, - "Returns true when class is already present" - ); -}); - -QUnit.test("Removes class with second argument", function(assert) { - var cList = document.createElement("p").classList; - - cList.add("c1"); - cList.toggle("c1", false); - assert.ok(!cList.contains("c1"), "Removes a class"); - - assert.strictEqual( - cList.toggle("c2", false), - false, - "Returns false when class is removed" - ); - - cList.toggle("c3", false); - assert.ok( - !cList.contains("c3"), - "Does not add a class that is not present" - ); - - assert.strictEqual( - cList.toggle("c4", false), - false, - "Returns false when class was not present" - ); -}); From a0570036ea2bdb5724ae9a08efd99752abbbbdf6 Mon Sep 17 00:00:00 2001 From: Doug Beck Date: Sun, 30 Jul 2017 15:59:39 -0400 Subject: [PATCH 4/4] Update public ci --- .travis.yml | 4 ++-- README.md | 4 ++++ test/wdio.defaults.js | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3147878..267ae50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -script: script/test +script: npm run test:ci language: node_js node_js: -- '0.10' +- 6 diff --git a/README.md b/README.md index 6dbeb30..013957d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # classList.js +[![Test Status](https://saucelabs.com/buildstatus/classlist-polyfill)][sauce] + +[sauce]: https://saucelabs.com/u/classlist-polyfill + Cross-browser JavaScript shim that fully implements `element.classList`. Refer to [the MDN page on `element.classList`][MDN] for more information. diff --git a/test/wdio.defaults.js b/test/wdio.defaults.js index 5927e62..033c6e5 100644 --- a/test/wdio.defaults.js +++ b/test/wdio.defaults.js @@ -26,6 +26,11 @@ exports.config = { global.expect = chai.expect; const session = browser.session().value; global.agent = `${session.browserName} ${session.browserVersion}`; + if (process.env.TRAVIS_BUILD_NUMBER) { + // enable saucelab badges + capabilities.public = true; + capabilities.build = process.env.TRAVIS_BUILD_NUMBER; + } }, baseUrl: 'http://localhost:4567',