Visual test setup up and running

This commit is contained in:
squidfunk 2017-01-28 23:50:33 +01:00
parent 7d46ed2a85
commit 17808d964d
13 changed files with 309 additions and 143 deletions

View File

@ -21,7 +21,7 @@
*/
/* ----------------------------------------------------------------------------
* Definition
* Module
* ------------------------------------------------------------------------- */
export default /* JSX */ {

View File

@ -29,8 +29,9 @@ import util from "gulp-util"
* Task: generate visual tests
* ------------------------------------------------------------------------- */
export default (gulp, config) => {
export default (gulp, config, args) => {
const theme = path.resolve(process.cwd(), config.views.build)
const match = new RegExp(args.grep || "", "i")
return () => {
return gulp.src(`${config.tests.visual}/suites/**/mkdocs.yml`)
.pipe(
@ -45,16 +46,18 @@ export default (gulp, config) => {
`${config.tests.visual}/data`, name, "_")
/* Generate test fixtures with freshly built theme */
const proc = child.spawnSync("mkdocs", [
"build", "--site-dir", site, "--theme-dir", theme
], {
cwd: path.dirname(file.path)
})
if (match.test(name)) {
const proc = child.spawnSync("mkdocs", [
"build", "--site-dir", site, "--theme-dir", theme
], {
cwd: path.dirname(file.path)
})
/* Emit error, if any */
if (proc.status)
this.emit("error", new util.PluginError("mkdocs",
`Terminated with errors: ${proc.stderr.toString()}`))
/* Emit error, if any */
if (proc.status)
this.emit("error", new util.PluginError("mkdocs",
`Terminated with errors: ${proc.stderr.toString()}`))
}
/* Terminate */
done()

View File

@ -76,8 +76,8 @@ export default (gulp, config, args) => {
const gemini = require(
path.join(process.cwd(), `${config.tests.visual}/config`,
process.env.CI || process.env.SAUCE
? "gemini-sauce.json"
: "gemini-local.json"))
? "gemini.sauce.json"
: "gemini.local.json"))
/* Start Gemini and return runner upon finish */
return new Gemini(gemini).test(`${config.tests.visual}/suites`, {

View File

@ -1,87 +0,0 @@
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

View File

@ -0,0 +1,152 @@
/*
* 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 config from "../config.json"
/* ----------------------------------------------------------------------------
* Helper
* ------------------------------------------------------------------------- */
/**
* Resolve relevant breakpoints matching expression
*
* The breakpoints are assumed to be specified by their names set in the
* configuration file, prefixed with an "@" character.
*
* There are three selection modes:
*
* 1. -@bp: The specified breakpoint and all preceding
* 2. @bp: Only the specified breakpoint
* 3. +@bp: The specified breakpoint and all following
*
* @param {Array.<object>} breakpoints - Breakpoints
* @param {string} expr - Expression
* @return {Array.<object>} Selected breakpoints
*/
const resolve = (breakpoints, expr) => {
if (typeof expr === "undefined")
return breakpoints
/* Split expression and find the offset of the specified breakpoint */
const [mode, name] = expr.split("@")
const index = breakpoints.findIndex(
breakpoint => breakpoint.name === name)
/* Determine whether to go up or down */
const from = mode !== "-" ? index : 0
const to = mode !== "+" ? index + 1 : breakpoints.length
/* Return relevant breakpoints */
return breakpoints.slice(from, to)
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* TODO
*/
const generate = components => {
/* Generate a suite for every component */
for (const name of Object.keys(components)) {
const component = components[name]
/* Create suite */
gemini.suite(name, suite => {
if (component.url)
suite.setUrl(component.url)
/* The capture selector is assumed to exist */
suite.setCaptureElements(component.capture)
/* Resolve and apply relevant breakpoints */
const breakpoints = resolve(config.breakpoints, component.break)
for (const breakpoint of breakpoints) {
suite.capture(`@${breakpoint.name}`, actions => {
actions.setWindowSize(breakpoint.size.width, breakpoint.size.height)
})
}
/* Generate sub-suites */
generate(component.suite || {})
})
/* Set component states to default, if none given */
// const states = component.states
// ? component.states
// : [{ name: "", wait: 0 }]
//
// let done = 0
// for (const state of states) {
// gemini.suite(`${name}${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 breakpoints = resolve(config.breakpoints, component.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
}

View File

@ -1,4 +1,4 @@
# Admonition Tests
# Suite
<style>
.md-header {

View File

@ -20,109 +20,86 @@
* IN THE SOFTWARE.
*/
import generate from "../../../generate.js"
import spec from "~/tests/visual/helpers/spec"
/* ----------------------------------------------------------------------------
* Tests
* ------------------------------------------------------------------------- */
generate({
/*
* Admonition block
*
* The admonition block looks the same on everything above tablet
* portrait, so we can save a few test cases.
*/
/*
* Admonition block
*
* The admonition block looks the same on everything above tablet
* portrait, so we can save a few test cases.
*/
spec.generate({
"admonition": {
"url": "/extensions/admonition/_",
"capture": "#default + .admonition",
"break": "-@tablet-portrait",
"suite": {
/*
* Admonition block with a custom title
*/
/* Admonition block with a custom title */
"#custom-title": {
"capture": "#custom-title + .admonition",
"break": "@screen"
},
/*
* Admonition block with a long title
*/
/* Admonition block with a long title */
"#long-title": {
"capture": "#long-title + .admonition",
"break": "@screen"
},
/*
* Admonition block with an empty title
*/
/* Admonition block with an empty title */
"#empty-title": {
"capture": "#empty-title + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "note"
*/
/* Admonition block of type "note" */
"#note": {
"capture": "#note + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "summary"
*/
/* Admonition block of type "summary" */
"#summary": {
"capture": "#summary + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "tip"
*/
/* Admonition block of type "tip" */
"#tip": {
"capture": "#tip + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "success"
*/
/* Admonition block of type "success" */
"#success": {
"capture": "#success + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "warning"
*/
/* Admonition block of type "warning" */
"#warning": {
"capture": "#warning + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "failure"
*/
/* Admonition block of type "failure" */
"#failure": {
"capture": "#failure + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "danger"
*/
/* Admonition block of type "danger" */
"#danger": {
"capture": "#danger + .admonition",
"break": "@screen"
},
/*
* Admonition block with style "bug"
*/
/* Admonition block of type "bug" */
"#bug": {
"capture": "#bug + .admonition",
"break": "@screen"

View File

@ -0,0 +1,58 @@
# Suite
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam condimentum
lacinia urna id vestibulum. Maecenas tincidunt nulla dui, a dapibus turpis
iaculis at. Donec tortor sem, scelerisque ut congue id, pretium ac risus.
Vivamus ac quam semper, elementum neque nec, dictum sem. Nullam porttitor in
ipsum quis sagittis. Cras viverra egestas purus ullamcorper eleifend. Nunc id
facilisis magna, id sagittis metus. Suspendisse egestas, ipsum sed consectetur
pretium, mauris tortor eleifend sem, vel cursus diam augue at diam. Nullam
accumsan id sapien feugiat ultrices. Cras tempor nulla a maximus dignissim.
Aliquam sed orci et elit tempor bibendum ac non neque. Aliquam erat volutpat.
Duis eu ligula ullamcorper, tristique libero in, eleifend turpis. Cras
fermentum iaculis ipsum, vitae viverra enim posuere vel. Phasellus non
fermentum arcu. Donec pulvinar erat non tellus rhoncus, bibendum interdum
libero ornare.
Etiam vel commodo turpis. Proin imperdiet ante eu suscipit ullamcorper.
Vivamus pharetra, mauris nec bibendum suscipit, dui velit vehicula purus,
sit amet pretium ex felis quis tellus. Nunc urna purus, dignissim in justo
quis, tempus sollicitudin odio. Morbi in commodo leo. Vestibulum fringilla
arcu quis venenatis venenatis. Phasellus vitae est at magna aliquam hendrerit.
Sed egestas, dui sit amet convallis porttitor, velit lectus molestie ipsum,
non ullamcorper erat elit vitae purus. In pulvinar nisl sed nulla placerat,
ac sollicitudin felis varius. Nunc orci quam, cursus ut fermentum eu,
interdum id dolor. Etiam tincidunt est elit, at tempus ligula pretium quis.
In vitae leo ullamcorper, lobortis nibh at, varius risus. Lorem ipsum dolor sit
amet, consectetur adipiscing elit. Sed risus neque, mattis a urna in, gravida
bibendum odio. Quisque enim nunc, auctor id justo a, viverra tempor dolor.
Duis molestie sagittis justo, id euismod mauris volutpat et. Suspendisse sed
leo vitae eros pulvinar scelerisque ut eu dolor. Nam at sapien dui. Integer
mattis faucibus metus. Pellentesque habitant morbi tristique senectus et netus
et malesuada fames ac turpis egestas. Mauris non sapien eleifend, eleifend
lectus at, elementum metus. Maecenas in tortor ut dui venenatis venenatis
sed id erat. Duis felis leo, eleifend a orci a, iaculis hendrerit arcu.
Praesent eget tellus tellus. Mauris eleifend mauris vitae porta laoreet. Morbi
venenatis, eros consectetur faucibus sodales, sapien purus interdum erat, quis
ultricies lacus odio sit amet tellus. Sed tincidunt est vitae sapien tempor
elementum.
Morbi ac eros ultrices, pulvinar ante ut, gravida risus. Integer id dolor
rhoncus odio scelerisque vestibulum. Integer justo felis, finibus congue felis
in, efficitur bibendum libero. Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Aenean dignissim enim ac justo cursus condimentum. Class
aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos
himenaeos. Morbi euismod pulvinar lacus sit amet egestas. Praesent in ex
molestie, scelerisque diam id, malesuada mauris. Vivamus at magna eu tellus
cursus hendrerit eu nec felis. Suspendisse ut hendrerit ex. Nulla semper quam
nec tincidunt vestibulum. Donec non nibh elit. Donec neque lacus, consequat
vitae nibh et, faucibus mollis dolor.
Nunc tempus lectus odio, sed laoreet elit suscipit et. Cras rutrum nibh eget
tellus tempus, et sodales sapien varius. Nam quis mi sagittis lacus commodo
cursus et viverra nibh. Vivamus ut egestas ante. Proin scelerisque tortor
turpis, at facilisis tortor feugiat mollis. Suspendisse neque odio, efficitur
quis ipsum a, tristique rutrum purus. Fusce ac tellus in magna eleifend
aliquet. Aliquam lectus libero, varius id nibh a, gravida fermentum est.
Aliquam erat volutpat.

View File

@ -0,0 +1,26 @@
# 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.
# Test suite
site_name: Navigation Test
pages:
- Lorem ipsum dolor sit amet: index.md
- Consectetur adipiscing elit: empty.md
- Etiam condimentum lacinia urna id vestibulum: empty.md

View File

@ -0,0 +1,37 @@
/*
* 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 spec from "~/tests/visual/helpers/spec"
/* ----------------------------------------------------------------------------
* Tests
* ------------------------------------------------------------------------- */
/*
* TODO
*/
spec.generate({
"md-nav--primary": {
"url": "/layout/nav/_",
"capture": ".md-nav--primary"
}
})