1 | const R = require('ramda')
|
2 | const fs = require('fs')
|
3 | const path = require('path')
|
4 | const debug = require('debug')('spdt')
|
5 | const { getConfig, STORYBOOK_PORT, SPDT_DIR } = require('./config')
|
6 |
|
7 | const saveFile = (fileName, content) => {
|
8 | fs.writeFile(fileName, content, (err) => {
|
9 | if (err) {
|
10 | return debug(`error: ${err}`)
|
11 | } else {
|
12 | return debug(`File was saved: ${fileName}`)
|
13 | }
|
14 | })
|
15 | }
|
16 |
|
17 | const generateImports = () => `const TIMEOUT = 5000
|
18 | `
|
19 |
|
20 | const generateDescribe = (componentName, fixtureName, itTests) => `
|
21 | describe(
|
22 | '${componentName} - fixture ${fixtureName}',
|
23 | () => {
|
24 | let page
|
25 | beforeEach(async () => {
|
26 | // eslint-disable-next-line no-underscore-dangle
|
27 | page = await global.__BROWSER__.newPage()
|
28 | // prettier-ignore
|
29 | await page.goto(
|
30 | 'http://localhost:${STORYBOOK_PORT}/iframe.html?path=/story/${componentName.toLowerCase()}--${fixtureName.toLowerCase()}',
|
31 | { waitUntil: 'load' }
|
32 | )
|
33 | }, TIMEOUT)
|
34 |
|
35 | afterEach(async () => {
|
36 | await page.close()
|
37 | })
|
38 | ${itTests}
|
39 | },
|
40 | TIMEOUT,
|
41 | )
|
42 | `
|
43 |
|
44 | const testCheckSelectorObject = (strOrObj) => {
|
45 | let selector
|
46 | let length
|
47 | if (typeof strOrObj === 'string') {
|
48 | selector = strOrObj
|
49 | length = 1
|
50 | } else if (typeof strOrObj === 'object' && !Array.isArray(strOrObj)) {
|
51 | ;({ length, selector } = strOrObj)
|
52 | }
|
53 |
|
54 | if (!selector || !length) {
|
55 | return null
|
56 | }
|
57 | return `
|
58 | it('checkSelector: should find component matching selector [${selector}] ${length} time(s)', async () => {
|
59 | const components = await page.$$('${selector}')
|
60 | const expected = ${length}
|
61 | expect(components).toHaveLength(expected)
|
62 | })`
|
63 | }
|
64 |
|
65 | const testCheckSelector = (fixture) => {
|
66 | const { checkSelector } = R.propOr({}, 'spdt', fixture)
|
67 | if (!checkSelector) {
|
68 | return null
|
69 | }
|
70 | if (typeof checkSelector === 'string') {
|
71 | return testCheckSelectorObject(checkSelector)
|
72 | }
|
73 | if (Array.isArray(checkSelector)) {
|
74 | return checkSelector.map(testCheckSelectorObject)
|
75 | } else if (typeof checkSelector === 'object') {
|
76 | return testCheckSelectorObject(checkSelector)
|
77 | }
|
78 | return null
|
79 | }
|
80 |
|
81 | const testCheckAttrObject = ({ selector, attribute, expected }) => {
|
82 | if (!selector || !expected || !attribute) {
|
83 | return null
|
84 | }
|
85 | return `
|
86 | it('checkAttr: should find component matching selector [${selector}] with attribute [${attribute}]', async () => {
|
87 | const actual = await page.$eval('${selector}', element => element.getAttribute('${attribute}'))
|
88 | expect(actual).toEqual('${expected}')
|
89 | })`
|
90 | }
|
91 |
|
92 | const testCheckAttr = (fixture) => {
|
93 | const { checkAttr } = (fixture && fixture.spdt) || {}
|
94 | if (!checkAttr) {
|
95 | return null
|
96 | }
|
97 | if (Array.isArray(checkAttr)) {
|
98 | return checkAttr.map(testCheckAttrObject)
|
99 | } else if (typeof checkAttr === 'object') {
|
100 | return testCheckAttrObject(checkAttr)
|
101 | }
|
102 | return null
|
103 | }
|
104 |
|
105 | const testCheckSvg = (fixture) => {
|
106 | const { checkSvg } = R.propOr({}, 'spdt', fixture)
|
107 | if (checkSvg !== true) {
|
108 | return null
|
109 | }
|
110 | return `
|
111 | it('checkSvg: should load component as <svg>', async () => {
|
112 | const component = await page.$('svg')
|
113 | expect(component._remoteObject.description).toMatch('svg') // eslint-disable-line no-underscore-dangle
|
114 | })`
|
115 | }
|
116 |
|
117 | const testCheckAxes = (fixture) => {
|
118 | const { checkAxes } = R.propOr({}, 'spdt', fixture)
|
119 | if (!Number.isInteger(checkAxes)) {
|
120 | return null
|
121 | }
|
122 | return `
|
123 | it('checkAxes: should have ${checkAxes} axes', async () => {
|
124 | const axes = await page.$$('g.axis')
|
125 | const expected = ${checkAxes}
|
126 | expect(axes).toHaveLength(expected)
|
127 | })`
|
128 | }
|
129 |
|
130 | const testCheckBars = (fixture) => {
|
131 | const { checkBars } = R.propOr({}, 'spdt', fixture)
|
132 | if (checkBars !== true) {
|
133 | return null
|
134 | }
|
135 | const checkBarsValue = R.pathOr(0, ['props', 'data', 'length'], fixture)
|
136 | return `
|
137 | it('checkBars: should have ${checkBarsValue} bars according to fixture data', async () => {
|
138 | const bars = await page.$$('rect.bar')
|
139 | const expected = ${checkBarsValue} // fixture.props.data.length
|
140 | expect(bars).toHaveLength(expected)
|
141 | })`
|
142 | }
|
143 |
|
144 | const testCheckArcs = (fixture) => {
|
145 | const { checkArcs } = R.propOr({}, 'spdt', fixture)
|
146 | if (checkArcs !== true) {
|
147 | return null
|
148 | }
|
149 | const checkArcsValue = R.pathOr(0, ['props', 'data', 'length'], fixture)
|
150 | return `
|
151 | it('checkArcs: should have ${checkArcsValue} arcs according to fixture data', async () => {
|
152 | const arcs = await page.$$('path.arc')
|
153 | const expected = ${checkArcsValue} // fixture.props.data.length
|
154 | expect(arcs).toHaveLength(expected)
|
155 | })`
|
156 | }
|
157 |
|
158 | const config = getConfig()
|
159 | let customMapper
|
160 | try {
|
161 | const mapperPath = path.resolve(
|
162 | config.projectRoot,
|
163 | SPDT_DIR,
|
164 | 'test-declarations.js',
|
165 | )
|
166 |
|
167 | customMapper = require(mapperPath)
|
168 | } catch (error) {
|
169 | debug(`Caught exception: ${error.message}`)
|
170 | throw error
|
171 | }
|
172 |
|
173 |
|
174 | const testMapper = {
|
175 | checkSelector: testCheckSelector,
|
176 | checkSvg: testCheckSvg,
|
177 | checkAxes: testCheckAxes,
|
178 | checkBars: testCheckBars,
|
179 | checkArcs: testCheckArcs,
|
180 | ...customMapper,
|
181 | }
|
182 |
|
183 | const generateItTests = (fixture) => {
|
184 | const result = []
|
185 | const spdtKeys = Object.keys(R.propOr({}, 'spdt', fixture))
|
186 | spdtKeys.forEach((checkItem) => {
|
187 | if (testMapper[checkItem]) {
|
188 | const test = testMapper[checkItem](fixture)
|
189 | result.push(Array.isArray(test) ? test.join('') : test)
|
190 | }
|
191 | })
|
192 | return result.join('')
|
193 | }
|
194 |
|
195 | function getFileName({ file, pathToTestIndex }) {
|
196 | const pathTo = pathToTestIndex || getConfig().pathToTestIndex
|
197 | return `${pathTo}/${file.replace('.story.', '.generated.spdt.')}`
|
198 | }
|
199 |
|
200 | function generateContent({ fixtures, componentName }) {
|
201 | const content = []
|
202 | content.push(generateImports(componentName))
|
203 |
|
204 | Object.keys(fixtures).forEach((fixtureName) => {
|
205 | const itTests = generateItTests(fixtures[fixtureName])
|
206 | content.push(generateDescribe(componentName, fixtureName, itTests))
|
207 | })
|
208 | return content
|
209 | }
|
210 |
|
211 | function testGenerator({ fixtures, file, componentName, pathToTestIndex }) {
|
212 | const content = generateContent({ fixtures, componentName })
|
213 | const fileName = getFileName({ file, pathToTestIndex })
|
214 | saveFile(fileName, content.join(''))
|
215 | }
|
216 |
|
217 | module.exports = {
|
218 | testGenerator,
|
219 | getFileName,
|
220 | generateContent,
|
221 | generateItTests,
|
222 | testCheckArcs,
|
223 | testCheckBars,
|
224 | testCheckSvg,
|
225 | testCheckSelector,
|
226 | testCheckAxes,
|
227 | saveFile,
|
228 | testCheckAttr,
|
229 | }
|