2017-01-29 01:50:33 +03:00
|
|
|
/*
|
2017-02-02 01:56:57 +03:00
|
|
|
* 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.
|
|
|
|
*/
|
2017-01-29 01:50:33 +03:00
|
|
|
|
|
|
|
import config from "../config.json"
|
2017-02-02 01:56:57 +03:00
|
|
|
import path from "path"
|
2017-02-09 19:35:50 +03:00
|
|
|
import yargs from "yargs"
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
|
|
* Configuration and arguments
|
|
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/* Parse arguments from command line */
|
|
|
|
const args = yargs.argv
|
2017-01-29 01:50:33 +03:00
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------------
|
2017-02-02 01:56:57 +03:00
|
|
|
* Functions
|
2017-01-29 01:50:33 +03:00
|
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
}
|
|
|
|
|
2017-02-02 01:56:57 +03:00
|
|
|
/**
|
2017-02-09 19:35:50 +03:00
|
|
|
* Filter a set of test suites using a regular expression
|
|
|
|
*
|
|
|
|
* @param {Array.<object>} components - Component specifications
|
|
|
|
* @param {Array.<string>} parent - Parent test suite names
|
|
|
|
* @return {boolean} Whether at least one suite was kept
|
|
|
|
*/
|
|
|
|
const filter = (components, parent = []) => {
|
2017-02-12 18:53:04 +03:00
|
|
|
const pattern = new RegExp(args.grep.replace(/\s+/, ".*?"), "gi")
|
2017-02-09 19:35:50 +03:00
|
|
|
return Object.keys(components).reduce((match, name) => {
|
|
|
|
const component = components[name]
|
|
|
|
|
|
|
|
/* Deep-copy current path and call recursive */
|
|
|
|
const temp = parent.slice(0).concat(name)
|
|
|
|
const keep = filter(component.suite || {}, temp)
|
|
|
|
|
|
|
|
/* Remove all states that do not match the regular expression */
|
|
|
|
component.states = (component.states || [{ name: "", wait: 0 }]).reduce(
|
|
|
|
(states, state) => {
|
|
|
|
const fullname = temp.slice(0)
|
|
|
|
.concat(state.name.length ? [state.name] : [])
|
|
|
|
.join(" ")
|
2017-02-12 18:53:04 +03:00
|
|
|
if (fullname.match(pattern))
|
2017-02-09 19:35:50 +03:00
|
|
|
states.push(state)
|
|
|
|
return states
|
|
|
|
}, [])
|
|
|
|
|
2017-02-12 18:53:04 +03:00
|
|
|
/* Keep component, if there is at least one state or the component has
|
2017-02-09 19:35:50 +03:00
|
|
|
matching subsuites, so it needs to be kept */
|
2017-02-12 18:53:04 +03:00
|
|
|
if (component.states.length) {
|
|
|
|
return true
|
|
|
|
} else if (keep) {
|
|
|
|
delete component.capture
|
|
|
|
delete component.break
|
2017-02-09 19:35:50 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Otherwise, delete component */
|
|
|
|
delete components[name]
|
|
|
|
return match
|
|
|
|
}, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate Gemini test suites for the given components
|
2017-02-02 01:56:57 +03:00
|
|
|
*
|
|
|
|
* @param {string} dirname - Directory of the test suite
|
|
|
|
* @param {Array.<object>} components - Component specifications // TODO: document syntax and specificagtion
|
2017-01-29 01:50:33 +03:00
|
|
|
*/
|
2017-02-02 01:56:57 +03:00
|
|
|
const generate = (dirname, components) => {
|
|
|
|
const base = path.relative(`${__dirname}/../suites`, dirname)
|
2017-01-29 01:50:33 +03:00
|
|
|
|
|
|
|
/* Generate a suite for every component */
|
|
|
|
for (const name of Object.keys(components)) {
|
|
|
|
const component = components[name]
|
|
|
|
|
|
|
|
/* Create suite */
|
|
|
|
gemini.suite(name, suite => {
|
2017-02-02 02:40:36 +03:00
|
|
|
if (component.dir || component.url)
|
|
|
|
suite.setUrl(path.join(
|
|
|
|
base, component.dir ? component.dir : "",
|
|
|
|
"_", component.url ? component.url : ""))
|
2017-01-29 01:50:33 +03:00
|
|
|
|
|
|
|
/* The capture selector is assumed to exist */
|
2017-02-09 19:35:50 +03:00
|
|
|
if (component.capture)
|
|
|
|
suite.setCaptureElements(component.capture)
|
2017-01-29 01:50:33 +03:00
|
|
|
|
2017-02-02 01:56:57 +03:00
|
|
|
/* Generate a subsuite for every state */
|
2017-02-09 19:49:13 +03:00
|
|
|
const states = component.states || [{ name: "", wait: 0 }]
|
|
|
|
for (const state of states) {
|
2017-02-02 01:56:57 +03:00
|
|
|
const test = subsuite => {
|
|
|
|
|
2017-02-21 01:26:51 +03:00
|
|
|
/* Resolve and apply relevant breakpoints */
|
2017-02-02 01:56:57 +03:00
|
|
|
const breakpoints = resolve(config.breakpoints, component.break)
|
|
|
|
for (const breakpoint of breakpoints) {
|
|
|
|
subsuite.capture(`@${breakpoint.name}`, actions => {
|
|
|
|
|
2017-02-21 01:26:51 +03:00
|
|
|
/* Set window size according to breakpoint */
|
2017-02-02 01:56:57 +03:00
|
|
|
actions.setWindowSize(
|
2017-02-21 01:26:51 +03:00
|
|
|
breakpoint.size.width, breakpoint.size.height)
|
2017-02-02 01:56:57 +03:00
|
|
|
|
2017-02-21 01:26:51 +03:00
|
|
|
/* Add the name as a CSS class to the captured element */
|
2017-02-02 01:56:57 +03:00
|
|
|
if (state.name)
|
|
|
|
actions.executeJS(new Function(`
|
2017-02-09 19:49:13 +03:00
|
|
|
document.querySelector(
|
|
|
|
"${component.capture}"
|
|
|
|
).classList.add("${state.name}")
|
|
|
|
`))
|
2017-02-02 01:56:57 +03:00
|
|
|
|
2017-02-21 01:26:51 +03:00
|
|
|
/* Execute function inside an IIFE */
|
2017-02-02 01:56:57 +03:00
|
|
|
if (state.exec)
|
|
|
|
actions.executeJS(new Function(`(${state.exec})()`))
|
|
|
|
|
2017-02-21 01:26:51 +03:00
|
|
|
/* Wait the specified time before taking a screenshot */
|
2017-02-02 01:56:57 +03:00
|
|
|
if (state.wait)
|
|
|
|
actions.wait(state.wait)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-21 01:26:51 +03:00
|
|
|
/* No state sub-suite if the name is empty */
|
2017-02-02 01:56:57 +03:00
|
|
|
if (state.name.length > 0)
|
|
|
|
gemini.suite(state.name, subsuite => test(subsuite))
|
|
|
|
else
|
2017-02-21 01:26:51 +03:00
|
|
|
test(suite)
|
2017-01-29 01:50:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Generate sub-suites */
|
2017-02-02 01:56:57 +03:00
|
|
|
generate(dirname, component.suite || {})
|
2017-01-29 01:50:33 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-09 19:35:50 +03:00
|
|
|
/**
|
|
|
|
* Register Gemini test suites for the given components
|
|
|
|
*
|
|
|
|
* @param {string} dirname - Directory of the test suite
|
|
|
|
* @param {Array.<object>} components - Component specifications
|
|
|
|
*/
|
|
|
|
const register = (dirname, components) => {
|
|
|
|
if (args.grep)
|
|
|
|
filter(components)
|
|
|
|
generate(dirname, components)
|
|
|
|
}
|
|
|
|
|
2017-02-02 01:56:57 +03:00
|
|
|
/* ----------------------------------------------------------------------------
|
|
|
|
* Exports
|
|
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
|
2017-01-29 01:50:33 +03:00
|
|
|
export default {
|
2017-02-09 19:35:50 +03:00
|
|
|
register
|
2017-01-29 01:50:33 +03:00
|
|
|
}
|