Initial setup of Gemini and first testcases

This commit is contained in:
squidfunk 2017-01-18 23:23:45 +01:00
parent e68f54c79f
commit 203087bcb1
19 changed files with 601 additions and 27 deletions

View File

@ -22,3 +22,6 @@
/build /build
/material /material
/site /site
# Gemini reports
/gemini-report

20
.gemini.yml → .githooks/pre-commit/branch.sh Normal file → Executable file
View File

@ -1,3 +1,5 @@
#!/bin/bash
# Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com> # Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
@ -18,11 +20,15 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE. # IN THE SOFTWARE.
rootUrl: http://localhost:8000/ # Determine current branch
gridUrl: http://localhost:4444/wd/hub BRANCH=`git rev-parse --abbrev-ref HEAD`
echo -n "Hook[pre-commit]: Checking branch..."
# Browsers to run tests on # If we're on master, abort commit
browsers: if [[ "$BRANCH" == "master" ]]; then
chrome: echo "Commits on master are only allowed via Pull Requests. Aborting."
desiredCapabilities: exit 1
browserName: chrome fi
# We're good
exit 0

3
.gitignore vendored
View File

@ -31,6 +31,9 @@
/MANIFEST /MANIFEST
/site /site
# Gemini reports
/gemini-report
# Distribution files # Distribution files
/dist /dist
/mkdocs_material.egg-info /mkdocs_material.egg-info

View File

@ -35,7 +35,10 @@ const config = {
src: "src/assets", /* Source directory for assets */ src: "src/assets", /* Source directory for assets */
build: "material/assets" /* Target directory for assets */ build: "material/assets" /* Target directory for assets */
}, },
lib: "lib", /* Libraries */ lib: "lib", /* Libraries and tasks */
tests: {
visual: "tests/visual" /* Visual regression tests */
},
views: { views: {
src: "src", /* Source directory for views */ src: "src", /* Source directory for views */
build: "material" /* Target directory for views */ build: "material" /* Target directory for views */
@ -228,12 +231,11 @@ gulp.task("assets:clean", [
* Minify views * Minify views
*/ */
gulp.task("views:build", (args.revision ? [ gulp.task("views:build", (args.revision ? [
"assets:images:build", "assets:build"
"assets:stylesheets:build",
"assets:javascripts:build"
] : []).concat(args.clean ? [ ] : []).concat(args.clean ? [
"views:clean" "views:clean"
] : []), load("views/build")) ] : []),
load("views/build"))
/* /*
* Clean views * Clean views
@ -270,11 +272,19 @@ gulp.task("mkdocs:serve",
* Tests * Tests
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/*
* Run visual tests
*/
gulp.task("tests:visual:run", [
// "assets:build",
// "views:build"
], load("tests/visual/run"))
/* /*
* Start karma test runner * Start karma test runner
*/ */
gulp.task("tests:unit:watch", gulp.task("tests:unit:watch",
load("tests/unit/watch")) () => {})
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Interface * Interface
@ -286,9 +296,9 @@ gulp.task("tests:unit:watch",
gulp.task("build", [ gulp.task("build", [
"assets:build", "assets:build",
"views:build" "views:build"
].concat(args.mkdocs ].concat(args.mkdocs ? [
? "mkdocs:build" "mkdocs:build"
: [])) ] : []))
/* /*
* Clean assets and documentation * Clean assets and documentation

View File

@ -52,10 +52,6 @@ export const start = done => {
} }
} }
/* Register signal handler for all relevant events */
for (const signal of ["SIGTERM", "SIGINT", "exit"])
process.on(signal, stop)
/* Remember process handle */ /* Remember process handle */
server = server || proc server = server || proc
done() done()
@ -66,3 +62,10 @@ export const stop = () => {
if (server) if (server)
server.kill() server.kill()
} }
/* ----------------------------------------------------------------------------
* Signals
* ------------------------------------------------------------------------- */
for (const signal of ["SIGTERM", "SIGINT", "exit"])
process.on(signal, stop)

View File

@ -23,6 +23,7 @@
import path from "path" import path from "path"
import through from "through2" import through from "through2"
import util from "gulp-util" import util from "gulp-util"
import { CLIEngine } from "eslint" import { CLIEngine } from "eslint"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View File

@ -25,6 +25,7 @@ import gulpif from "gulp-if"
import mincss from "gulp-cssnano" import mincss from "gulp-cssnano"
import mqpacker from "css-mqpacker" import mqpacker from "css-mqpacker"
import postcss from "gulp-postcss" import postcss from "gulp-postcss"
import pseudoclasses from "postcss-pseudo-classes"
import rev from "gulp-rev" import rev from "gulp-rev"
import sass from "gulp-sass" import sass from "gulp-sass"
import sourcemaps from "gulp-sourcemaps" import sourcemaps from "gulp-sourcemaps"
@ -54,7 +55,9 @@ export default (gulp, config, args) => {
postcss([ postcss([
autoprefixer(), autoprefixer(),
mqpacker mqpacker
])) ].concat(!args.optimize ? [
pseudoclasses()
] : [])))
/* Minify sources */ /* Minify sources */
.pipe(gulpif(args.optimize, mincss())) .pipe(gulpif(args.optimize, mincss()))

View File

@ -39,7 +39,7 @@ export default () => {
server.kill() server.kill()
/* Spawn MkDocs server */ /* Spawn MkDocs server */
server = child.spawn("mkdocs", ["serve", "-a", "0.0.0.0:8000"], { server = child.spawn("mkdocs", ["serve", "--dev-addr", "0.0.0.0:8000"], {
stdio: "inherit" stdio: "inherit"
}) })
} }

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import child from "child_process"
import path from "path"
import * as selenium from "~/lib/servers/selenium"
import Gemini from "gemini"
/* ----------------------------------------------------------------------------
* Task: start test runner
* ------------------------------------------------------------------------- */
/* MkDocs server */
let server = null
/* ----------------------------------------------------------------------------
* Task: start test runner
* ------------------------------------------------------------------------- */
export default (gulp, config) => {
return () => {
/* Start MkDocs server */
return new Promise(resolve => {
server = child.spawn("mkdocs", [
"serve", "--dev-addr", "127.0.0.1:8000"
], {
cwd: config.tests.visual,
stdio: [process.stdin, process.stdout, "pipe"]
})
/* Wait for MkDocs server and resolve promise */
server.stderr.on("data", data => {
if (data.toString().match("Serving")) {
server.stderr.removeAllListeners("data")
resolve()
}
})
/* Start Selenium */
}).then(() => {
return new Promise(resolve => {
selenium.start(() => resolve())
/* Start Gemini test runner depending on environment */
}).then(() => {
const gemini = require(path.join(
process.cwd(), `${config.tests.visual}/.gemini-local.json`))
return new Gemini(gemini).test("tests/visual/suites", {
reporters: ["html"]
})
})
.then(() => {
selenium.stop()
})
})
.then(() => {
server.kill()
})
}
}

View File

@ -31,7 +31,7 @@ repo_url: https://github.com/squidfunk/mkdocs-material
# Copyright # Copyright
copyright: 'Copyright &copy; 2016 Martin Donath' copyright: 'Copyright &copy; 2016 Martin Donath'
# Documentation and theme # Theme directory
theme_dir: material theme_dir: material
# Options # Options

View File

@ -85,6 +85,7 @@
"mocha": "^3.2.0", "mocha": "^3.2.0",
"modularscale-sass": "^2.1.1", "modularscale-sass": "^2.1.1",
"node-notifier": "^4.6.1", "node-notifier": "^4.6.1",
"postcss-pseudo-classes": "^0.1.0",
"selenium-standalone": "^5.9.1", "selenium-standalone": "^5.9.1",
"stylelint": "^7.7.1", "stylelint": "^7.7.1",
"stylelint-config-standard": "^15.0.1", "stylelint-config-standard": "^15.0.1",

View File

@ -50,8 +50,8 @@
padding-bottom: 0.8rem; padding-bottom: 0.8rem;
transition: opacity 0.25s; transition: opacity 0.25s;
// [mobile landscape +]: Set proportional width // [tablet +]: Set proportional width
@include break-from-device(mobile landscape) { @include break-from-device(tablet) {
width: 50%; width: 50%;
} }
@ -68,8 +68,8 @@
// Title // Title
.md-footer-nav__title { .md-footer-nav__title {
// [mobile portrait -]: Hide title for previous page // [mobile -]: Hide title for previous page
@include break-to-device(mobile portrait) { @include break-to-device(mobile) {
display: none; display: none;
} }
} }

8
tests/visual/.eslintrc Normal file
View File

@ -0,0 +1,8 @@
{
"globals": {
"gemini": true
},
"rules": {
"no-loop-func": 0
}
}

View File

@ -0,0 +1,15 @@
{
"rootUrl": "http://localhost:8000",
"screenshotsDir": "./tests/visual/baseline",
"browsers": {
"local-chrome": {
"desiredCapabilities": {
"browserName": "chrome"
}
}
},
"system": {
"projectRoot": "./",
"sourceRoot": "src/assets/stylesheets"
}
}

39
tests/visual/break.json Normal file
View File

@ -0,0 +1,39 @@
{
"breakpoints": [
{
"name": "mobile-portrait",
"size": {
"width": 320,
"height": 600
}
},
{
"name": "mobile-landscape",
"size": {
"width": 480,
"height": 600
}
},
{
"name": "tablet-portrait",
"size": {
"width": 720,
"height": 600
}
},
{
"name": "tablet-landscape",
"size": {
"width": 960,
"height": 600
}
},
{
"name": "screen",
"size": {
"width": 1220,
"height": 600
}
}
]
}

View File

@ -0,0 +1,107 @@
# Admonition Tests
<style>
.md-header {
display: none;
}
</style>
## Default
!!! note
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
## Format
### Custom title
!!! note "Phasellus posuere in sem ut cursus"
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Long title
!!! note "Phasellus posuere in sem ut cursus. Nullam sit amet tincidunt ipsum, sit amet elementum turpis. Etiam ipsum quam, mattis in purus vitae, lacinia fermentum enim."
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Empty title
!!! note ""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
## Types
### Note
!!! note
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Summary
!!! summary
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Tip
!!! tip
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Success
!!! success
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Warning
!!! warning
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Failure
!!! failure
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Danger
!!! danger
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.
### Bug
!!! bug
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.

87
tests/visual/generate.js Normal file
View File

@ -0,0 +1,87 @@
const config = require("./break.json")
// TODO: also pass breakpoints to function!
const generate = components => {
for (const c of Object.keys(components)) {
const component = components[c]
// TODO: check states and generate a suite for each state!
// TODO: check name variants!
// TODO: build nested suites only once
// TODO: handle waiting/js
const states = component.states ? component.states :
[{ name: "", wait: 0 }]
let done = 0
for (const state of states) {
gemini.suite(`${c}${state.name}`, suite => {
/* Set URL of page to capture */
if (component.url)
suite.setUrl(component.url)
/* Set elements to capture */
if (component.capture)
suite.setCaptureElements(component.capture)
// TODO: otherwise throw error
if (component.break) {
const [mode, name] = component.break.split("@")
// get matching breakpoint. TODO: handle non-existent!!!
const b = config.breakpoints.findIndex(bp => {
return bp.name === name
})
// now split according to method
let breakpoints = []
switch (mode) {
case "":
breakpoints = config.breakpoints.slice(b, b + 1)
break
case "+":
breakpoints = config.breakpoints.slice(
b, config.breakpoints.length + 1)
break
case "-":
breakpoints = config.breakpoints.slice(0, b + 1)
break
}
// iterate breakpoints
for (const breakpoint of breakpoints) {
suite.capture(`@${breakpoint.name}`, actions => {
actions.setWindowSize(
breakpoint.size.width, breakpoint.size.height)
if (state.wait)
actions.wait(state.wait)
if (state.name) {
// eval, as its executed at the frontend
if (typeof state.name === "string") {
actions.executeJS(new Function(`
document.querySelector(
"${component.capture}"
).classList.add("${state.name}")
`)
)
} else {
actions.executeJS(state.name)
}
}
})
}
}
// nested suites
if (!done && component.suite) {
done = 1
generate(component.suite)
}
})
}
}
}
export default generate

75
tests/visual/mkdocs.yml Normal file
View File

@ -0,0 +1,75 @@
# Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
# Project information
site_name: Material for MkDocs Tests
site_description: A Material Design theme for MkDocs
site_author: Martin Donath
site_url: http://squidfunk.github.io/mkdocs-material/
# Repository
repo_name: squidfunk/mkdocs-material
repo_url: https://github.com/squidfunk/mkdocs-material
# Copyright
copyright: 'Copyright &copy; 2016 Martin Donath'
# Documentation and theme directories
docs_dir: fixtures
theme_dir: ../../material
# Options
extra:
palette:
primary: indigo
accent: indigo
social:
- type: github-alt
link: https://github.com/squidfunk
- type: twitter
link: https://twitter.com/squidfunk
- type: linkedin
link: https://de.linkedin.com/in/martin-donath-20a95039
# Extensions
markdown_extensions:
- markdown.extensions.admonition
- markdown.extensions.codehilite(guess_lang=false)
- markdown.extensions.footnotes
- markdown.extensions.meta
- markdown.extensions.toc(permalink=true)
- pymdownx.arithmatex
- pymdownx.betterem(smart_enable=all)
- pymdownx.caret
- pymdownx.critic
- pymdownx.emoji:
emoji_generator: !!python/name:pymdownx.emoji.to_svg
- pymdownx.inlinehilite
- pymdownx.magiclink
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.superfences
- pymdownx.tasklist(custom_checkbox=true)
- pymdownx.tilde
# Page tree
pages:
- Extensions:
- Admonition: extensions/admonition.md

View File

@ -0,0 +1,132 @@
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import generate from "../../generate.js"
/* ----------------------------------------------------------------------------
* Tests
* ------------------------------------------------------------------------- */
generate({
/*
* Admonition block
*
* The admonition block looks the same on everything above tablet
* portrait, so we can save a few testcases.
*/
".admonition": {
"url": "/extensions/admonition",
"capture": "#default + .admonition",
"break": "-@tablet-portrait",
"suite": {
/*
* Admonition block with a custom title
*/
"#custom-title": {
"capture": "#custom-title + .admonition",
"break": "@screen"
},
/*
* Admonition block with a long title
*/
"#long-title": {
"capture": "#long-title + .admonition",
"break": "@screen"
},
/*
* Admonition block with an empty title
*/
"#empty-title": {
"capture": "#empty-title + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "note"
*/
"#note": {
"capture": "#note + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "summary"
*/
"#summary": {
"capture": "#summary + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "tip"
*/
"#tip": {
"capture": "#tip + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "success"
*/
"#success": {
"capture": "#success + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "warning"
*/
"#warning": {
"capture": "#warning + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "failure"
*/
"#failure": {
"capture": "#failure + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "danger"
*/
"#danger": {
"capture": "#danger + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "bug"
*/
"#bug": {
"capture": "#bug + .admonition",
"break": "@screen"
}
}
}
})