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
/material
/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>
# 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
# IN THE SOFTWARE.
rootUrl: http://localhost:8000/
gridUrl: http://localhost:4444/wd/hub
# Determine current branch
BRANCH=`git rev-parse --abbrev-ref HEAD`
echo -n "Hook[pre-commit]: Checking branch..."
# Browsers to run tests on
browsers:
chrome:
desiredCapabilities:
browserName: chrome
# If we're on master, abort commit
if [[ "$BRANCH" == "master" ]]; then
echo "Commits on master are only allowed via Pull Requests. Aborting."
exit 1
fi
# We're good
exit 0

3
.gitignore vendored
View File

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

View File

@ -35,7 +35,10 @@ const config = {
src: "src/assets", /* Source 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: {
src: "src", /* Source directory for views */
build: "material" /* Target directory for views */
@ -228,12 +231,11 @@ gulp.task("assets:clean", [
* Minify views
*/
gulp.task("views:build", (args.revision ? [
"assets:images:build",
"assets:stylesheets:build",
"assets:javascripts:build"
"assets:build"
] : []).concat(args.clean ? [
"views:clean"
] : []), load("views/build"))
] : []),
load("views/build"))
/*
* Clean views
@ -270,11 +272,19 @@ gulp.task("mkdocs:serve",
* Tests
* ------------------------------------------------------------------------- */
/*
* Run visual tests
*/
gulp.task("tests:visual:run", [
// "assets:build",
// "views:build"
], load("tests/visual/run"))
/*
* Start karma test runner
*/
gulp.task("tests:unit:watch",
load("tests/unit/watch"))
() => {})
/* ----------------------------------------------------------------------------
* Interface
@ -286,9 +296,9 @@ gulp.task("tests:unit:watch",
gulp.task("build", [
"assets:build",
"views:build"
].concat(args.mkdocs
? "mkdocs:build"
: []))
].concat(args.mkdocs ? [
"mkdocs:build"
] : []))
/*
* 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 */
server = server || proc
done()
@ -66,3 +62,10 @@ export const stop = () => {
if (server)
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 through from "through2"
import util from "gulp-util"
import { CLIEngine } from "eslint"
/* ----------------------------------------------------------------------------

View File

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

View File

@ -39,7 +39,7 @@ export default () => {
server.kill()
/* 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"
})
}

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 &copy; 2016 Martin Donath'
# Documentation and theme
# Theme directory
theme_dir: material
# Options

View File

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

View File

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