Skip to content

Commit fe0b63c

Browse files
lmiller1990Barthélémy Ledoux
and
Barthélémy Ledoux
authored
fix: improve handling of userland injected styles in component testing (#16024)
* feat(npm/react): do not clear head between tests * add a shared mount utils library * add readme * update dependencies * add mount utils to circle * change module Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
1 parent a6d504a commit fe0b63c

File tree

16 files changed

+321
-179
lines changed

16 files changed

+321
-179
lines changed

circle.yml

+13
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,15 @@ jobs:
12371237
path: npm/react/test_results
12381238
- store-npm-logs
12391239

1240+
npm-mount-utils:
1241+
<<: *defaults
1242+
steps:
1243+
- attach_workspace:
1244+
at: ~/
1245+
- run:
1246+
name: Build
1247+
command: yarn workspace @cypress/mount-utils build
1248+
- store-npm-logs
12401249

12411250
npm-create-cypress-tests:
12421251
<<: *defaults
@@ -1865,6 +1874,9 @@ linux-workflow: &linux-workflow
18651874
- npm-react:
18661875
requires:
18671876
- build
1877+
- npm-mount-utils:
1878+
requires:
1879+
- build
18681880
- npm-create-cypress-tests:
18691881
requires:
18701882
- build
@@ -1880,6 +1892,7 @@ linux-workflow: &linux-workflow
18801892
- npm-eslint-plugin-dev
18811893
- npm-create-cypress-tests
18821894
- npm-react
1895+
- npm-mount-utils
18831896
- npm-vue
18841897
- npm-design-system
18851898
- npm-webpack-batteries-included-preprocessor

npm/mount-utils/CHANGELOG.md

Whitespace-only changes.

npm/mount-utils/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# @cypress/mount-utils
2+
3+
> **Note** this package is not meant to be used outside of cypress component testing.
4+
5+
This librares exports some shared types and utility functions designed to build adapters for components frameworks.
6+
7+
It is used in:
8+
9+
- [`@cypress/react`](https://github.com/cypress-io/cypress/tree/develop/npm/react)
10+
- [`@cypress/vue`](https://github.com/cypress-io/cypress/tree/develop/npm/vue)

npm/mount-utils/package.json

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@cypress/mount-utils",
3+
"version": "0.0.0-development",
4+
"description": "Shared utilities for the various component testing adapters",
5+
"main": "dist/index.js",
6+
"scripts": {
7+
"build": "tsc",
8+
"build-prod": "tsc",
9+
"watch": "tsc -w"
10+
},
11+
"dependencies": {},
12+
"devDependencies": {
13+
"typescript": "^4.2.3"
14+
},
15+
"files": [
16+
"dist"
17+
],
18+
"license": "MIT",
19+
"repository": {
20+
"type": "git",
21+
"url": "https://github.com/cypress-io/cypress.git"
22+
},
23+
"homepage": "https://github.com/cypress-io/cypress/tree/master/npm/mount-utils#readme",
24+
"bugs": "https://github.com/cypress-io/cypress/issues/new?template=1-bug-report.md",
25+
"publishConfig": {
26+
"access": "public"
27+
}
28+
}

npm/react/src/utils.ts npm/mount-utils/src/index.ts

+81-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,66 @@
1-
import { StyleOptions } from './mount'
1+
/**
2+
* Additional styles to inject into the document.
3+
* A component might need 3rd party libraries from CDN,
4+
* local CSS files and custom styles.
5+
*/
6+
export interface StyleOptions {
7+
/**
8+
* Creates <link href="..." /> element for each stylesheet
9+
* @alias stylesheet
10+
*/
11+
stylesheets: string | string[]
12+
/**
13+
* Creates <link href="..." /> element for each stylesheet
14+
* @alias stylesheets
15+
*/
16+
stylesheet: string | string[]
17+
/**
18+
* Creates <style>...</style> element and inserts given CSS.
19+
* @alias styles
20+
*/
21+
style: string | string[]
22+
/**
23+
* Creates <style>...</style> element for each given CSS text.
24+
* @alias style
25+
*/
26+
styles: string | string[]
27+
/**
28+
* Loads each file and creates a <style>...</style> element
29+
* with the loaded CSS
30+
* @alias cssFile
31+
*/
32+
cssFiles: string | string[]
33+
/**
34+
* Single CSS file to load into a <style></style> element
35+
* @alias cssFile
36+
*/
37+
cssFile: string | string[]
38+
}
39+
40+
export const ROOT_ID = '__cy_root'
41+
42+
/**
43+
* Remove any style or extra link elements from the iframe placeholder
44+
* left from any previous test
45+
*
46+
*/
47+
export function cleanupStyles () {
48+
const styles = document.body.querySelectorAll('[data-cy=injected-style-tag]')
49+
50+
styles.forEach((styleElement) => {
51+
if (styleElement.parentElement) {
52+
styleElement.parentElement.removeChild(styleElement)
53+
}
54+
})
55+
56+
const links = document.body.querySelectorAll('[data-cy=injected-stylesheet]')
57+
58+
links.forEach((link) => {
59+
if (link.parentElement) {
60+
link.parentElement.removeChild(link)
61+
}
62+
})
63+
}
264

365
/**
466
* Insert links to external style resources.
@@ -14,6 +76,7 @@ function insertStylesheets (
1476
link.type = 'text/css'
1577
link.rel = 'stylesheet'
1678
link.href = href
79+
link.dataset.cy = 'injected-stylesheet'
1780
document.body.insertBefore(link, el)
1881
})
1982
}
@@ -25,6 +88,7 @@ function insertStyles (styles: string[], document: Document, el: HTMLElement | n
2588
styles.forEach((style) => {
2689
const styleElement = document.createElement('style')
2790

91+
styleElement.dataset.cy = 'injected-style-tag'
2892
styleElement.appendChild(document.createTextNode(style))
2993
document.body.insertBefore(styleElement, el)
3094
})
@@ -124,3 +188,19 @@ export const injectStylesBeforeElement = (
124188

125189
return insertLocalCssFiles(cssFiles, document, el, options.log)
126190
}
191+
192+
export function setupHooks (optionalCallback?: Function) {
193+
// When running component specs, we cannot allow "cy.visit"
194+
// because it will wipe out our preparation work, and does not make much sense
195+
// thus we overwrite "cy.visit" to throw an error
196+
Cypress.Commands.overwrite('visit', () => {
197+
throw new Error(
198+
'cy.visit from a component spec is not allowed',
199+
)
200+
})
201+
202+
beforeEach(() => {
203+
optionalCallback?.()
204+
cleanupStyles()
205+
})
206+
}

npm/mount-utils/tsconfig.json

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"compilerOptions": {
3+
/* Basic Options */
4+
"target": "es2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */,
5+
"module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
6+
"skipLibCheck": true,
7+
"lib": [
8+
"es2015",
9+
"dom"
10+
] /* Specify library files to be included in the compilation: */,
11+
"allowJs": true /* Allow javascript files to be compiled. */,
12+
// "checkJs": true, /* Report errors in .js files. */
13+
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
14+
"declaration": true /* Generates corresponding '.d.ts' file. */,
15+
// "sourceMap": true, /* Generates corresponding '.map' file. */
16+
// "outFile": "./", /* Concatenate and emit output to single file. */
17+
"outDir": "dist" /* Redirect output structure to the directory. */,
18+
// "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19+
// "removeComments": true, /* Do not emit comments to output. */
20+
// "noEmit": true, /* Do not emit outputs. */
21+
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
22+
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
23+
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
24+
25+
/* Strict Type-Checking Options */
26+
"strict": false /* Enable all strict type-checking options. */,
27+
// "noImplicitAny": true,
28+
29+
/* Module Resolution Options */
30+
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
31+
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
32+
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
33+
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
34+
// "typeRoots": [], /* List of folders to include type definitions from. */
35+
"types": ["cypress"] /* Type declaration files to be included in compilation. */,
36+
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
37+
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
38+
39+
/* Source Map Options */
40+
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
41+
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
42+
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
43+
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
44+
45+
/* Experimental Options */
46+
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
47+
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
48+
"esModuleInterop": true
49+
},
50+
"include": ["src"],
51+
"exclude": ["node_modules", "*.js"]
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as React from 'react'
2+
import { mount } from '@cypress/react'
3+
import styled, { ThemeProvider } from 'styled-components'
4+
5+
const lightest = '#FFFEFD'
6+
const light = '#FEFCF1'
7+
const darker = '#C49A03'
8+
const darkest = '#382E0A'
9+
10+
export const theme = {
11+
primaryDark: darkest,
12+
primaryLight: lightest,
13+
primaryLightDarker: light,
14+
primaryHover: darker,
15+
}
16+
17+
const styledComponentsStyle = 'margin-bottom:1rem'
18+
const Line = styled.div`
19+
${styledComponentsStyle}
20+
`
21+
22+
export const SearchResults = (props) => {
23+
return (
24+
<div>
25+
{props.results.map((result) => {
26+
return (
27+
<Line>
28+
{result.title}
29+
</Line>
30+
)
31+
})}
32+
</div>
33+
)
34+
}
35+
36+
const mountComponent = ({ results }, options) => {
37+
return mount(
38+
<ThemeProvider theme={theme}>
39+
<div style={{ margin: '6rem', maxWidth: '105rem' }}>
40+
<SearchResults results={results} />
41+
</div>
42+
</ThemeProvider>,
43+
options,
44+
)
45+
}
46+
47+
const inlineStyle = 'body { background: blue; }'
48+
const bulmaCDN = 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css'
49+
50+
describe('SearchResults', () => {
51+
it('should inject styles into <head>', () => {
52+
mountComponent({
53+
results: [{ title: 'Org 1' }, { title: 'Org 2' }],
54+
},
55+
{
56+
stylesheets: [bulmaCDN],
57+
style: inlineStyle,
58+
})
59+
60+
cy.get('link').should('exist')
61+
cy.get('link').should('have.attr', 'href', bulmaCDN)
62+
})
63+
64+
it('style-components injected styles from previous test should not be cleaned up \
65+
but styles and stylesheets in mount should be', () => {
66+
// style-components injected style should NOT have bene cleaned up
67+
cy.get('style').should('contain.text', styledComponentsStyle)
68+
69+
// cleaned up inline <style> from previous test
70+
cy.get('style').should('not.contain.text', inlineStyle)
71+
72+
// cleaned up bulma CDN link from previous test
73+
cy.get('link').should('not.exist')
74+
75+
mountComponent({
76+
results: [{ title: 'Org 1' }, { title: 'Org 2' }],
77+
})
78+
})
79+
})

npm/react/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"watch": "yarn build --watch --watch.exclude ./dist/**/*"
1818
},
1919
"dependencies": {
20+
"@cypress/mount-utils": "0.0.0-development",
2021
"@cypress/webpack-preprocessor": "0.0.0-development",
2122
"debug": "4.3.2",
2223
"find-webpack": "2.2.1",

npm/react/rollup.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function createEntry (options) {
2424
external: [
2525
'react',
2626
'react-dom',
27+
'@cypress/mount-utils',
2728
],
2829
plugins: [
2930
resolve(), commonjs(),

npm/react/src/hooks.ts

-38
This file was deleted.

0 commit comments

Comments
 (0)