From 4bcdb06fc1611b07d364c6d57e2dc46c04151324 Mon Sep 17 00:00:00 2001 From: Jean-Michel Leclercq Date: Tue, 18 Aug 2020 01:23:12 +0200 Subject: [PATCH] feat: Provide custom external css/style (#158) * Bootstrap test * Material UI test * skip until works * Custom test style --- examples/ng9/angular.json | 5 +- examples/ng9/package-lock.json | 21 ++++ examples/ng9/package.json | 2 + .../src/app/add-style/add-style.component.css | 3 + .../add-style/add-style.component.cy-spec.ts | 19 +++ .../app/add-style/add-style.component.html | 3 + .../src/app/add-style/add-style.component.ts | 15 +++ examples/ng9/src/app/app.module.ts | 12 +- .../bootstrap-button.component.css | 0 .../bootstrap-button.component.cy-spec.ts | 17 +++ .../bootstrap-button.component.html | 1 + .../bootstrap-button.component.ts | 15 +++ .../material-button.component.css | 0 .../material-button.component.cy-spec.ts | 16 +++ .../material-button.component.html | 2 + .../material-button.component.ts | 16 +++ examples/ng9/src/app/work.cy-spec.ts | 2 - examples/ng9/src/index.html | 4 +- examples/ng9/src/styles.css | 3 + lib/config.ts | 45 ++++++- lib/css-utils.ts | 118 ++++++++++++++++++ lib/index.ts | 9 ++ support.js | 2 +- 23 files changed, 321 insertions(+), 9 deletions(-) create mode 100644 examples/ng9/src/app/add-style/add-style.component.css create mode 100644 examples/ng9/src/app/add-style/add-style.component.cy-spec.ts create mode 100644 examples/ng9/src/app/add-style/add-style.component.html create mode 100644 examples/ng9/src/app/add-style/add-style.component.ts create mode 100644 examples/ng9/src/app/bootstrap-button/bootstrap-button.component.css create mode 100644 examples/ng9/src/app/bootstrap-button/bootstrap-button.component.cy-spec.ts create mode 100644 examples/ng9/src/app/bootstrap-button/bootstrap-button.component.html create mode 100644 examples/ng9/src/app/bootstrap-button/bootstrap-button.component.ts create mode 100644 examples/ng9/src/app/material-button/material-button.component.css create mode 100644 examples/ng9/src/app/material-button/material-button.component.cy-spec.ts create mode 100644 examples/ng9/src/app/material-button/material-button.component.html create mode 100644 examples/ng9/src/app/material-button/material-button.component.ts create mode 100644 lib/css-utils.ts diff --git a/examples/ng9/angular.json b/examples/ng9/angular.json index 2daa8cc5..3ed9c9c3 100644 --- a/examples/ng9/angular.json +++ b/examples/ng9/angular.json @@ -24,6 +24,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], "scripts": [], @@ -90,6 +91,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], "scripts": [] @@ -121,6 +123,7 @@ } } } - }}, + } + }, "defaultProject": "angular-project" } \ No newline at end of file diff --git a/examples/ng9/package-lock.json b/examples/ng9/package-lock.json index 650dfc7f..b47dee6c 100644 --- a/examples/ng9/package-lock.json +++ b/examples/ng9/package-lock.json @@ -282,6 +282,22 @@ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.11.tgz", "integrity": "sha512-VKAExUnEJfo1PDQKagpx2pn+QMZCsPLRiADzTdl4U0VPylK3ALbn4ZNY9UbdwyE2plitz++LkH7sEGGfh+PNrQ==" }, + "@angular/cdk": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.4.tgz", + "integrity": "sha512-iw2+qHMXHYVC6K/fttHeNHIieSKiTEodVutZoOEcBu9rmRTGbLB26V/CRsfIRmA1RBk+uFYWc6UQZnMC3RdnJQ==", + "requires": { + "parse5": "^5.0.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + } + } + }, "@angular/cli": { "version": "9.1.10", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.1.10.tgz", @@ -552,6 +568,11 @@ "integrity": "sha512-jfm4etbqldj6MTwECwyoAs7tXEAR8K/8P8dBZnsELhY+V8oFidTJI3NY52PB3Ym7leSPorYdOAeUMMuQfPaVxg==", "dev": true }, + "@angular/material": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.2.4.tgz", + "integrity": "sha512-LkoTXE6B0slvMhvfZDdPWaz4yaYLkaAp5VSPunI9pxGsPxzqEV9e210wC1/sjG/76Nk8Ep7/2z9XKac8Q9bMwA==" + }, "@angular/platform-browser": { "version": "9.1.11", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.1.11.tgz", diff --git a/examples/ng9/package.json b/examples/ng9/package.json index 9db102b2..465cd111 100644 --- a/examples/ng9/package.json +++ b/examples/ng9/package.json @@ -15,10 +15,12 @@ "private": true, "dependencies": { "@angular/animations": "9.1.11", + "@angular/cdk": "9.2.4", "@angular/common": "9.1.11", "@angular/compiler": "9.1.11", "@angular/core": "9.1.11", "@angular/forms": "9.1.11", + "@angular/material": "9.2.4", "@angular/platform-browser": "9.1.11", "@angular/platform-browser-dynamic": "9.1.11", "@angular/router": "9.1.11", diff --git a/examples/ng9/src/app/add-style/add-style.component.css b/examples/ng9/src/app/add-style/add-style.component.css new file mode 100644 index 00000000..c01fae47 --- /dev/null +++ b/examples/ng9/src/app/add-style/add-style.component.css @@ -0,0 +1,3 @@ +.my-class { + color: red; +} \ No newline at end of file diff --git a/examples/ng9/src/app/add-style/add-style.component.cy-spec.ts b/examples/ng9/src/app/add-style/add-style.component.cy-spec.ts new file mode 100644 index 00000000..b23d04a8 --- /dev/null +++ b/examples/ng9/src/app/add-style/add-style.component.cy-spec.ts @@ -0,0 +1,19 @@ +import { initEnv, mount, setConfig } from 'cypress-angular-unit-test'; +import { AddStyleComponent } from './add-style.component'; + +describe('AddStyleComponent', () => { + + beforeEach(() => { + setConfig({ style: 'p {background-color: blue;}' }); + initEnv(AddStyleComponent); + }); + + it('should create', () => { + mount(AddStyleComponent); + cy.get('p') + .should('contain', 'add-style works!') + .should('have.css', 'color', 'rgb(255, 0, 0)') + .should('have.css', 'background-color', 'rgb(0, 0, 255)'); + }); + +}); diff --git a/examples/ng9/src/app/add-style/add-style.component.html b/examples/ng9/src/app/add-style/add-style.component.html new file mode 100644 index 00000000..52510dc7 --- /dev/null +++ b/examples/ng9/src/app/add-style/add-style.component.html @@ -0,0 +1,3 @@ +

+ add-style works! +

diff --git a/examples/ng9/src/app/add-style/add-style.component.ts b/examples/ng9/src/app/add-style/add-style.component.ts new file mode 100644 index 00000000..b4b41479 --- /dev/null +++ b/examples/ng9/src/app/add-style/add-style.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-add-style', + templateUrl: './add-style.component.html', + styleUrls: ['./add-style.component.css'] +}) +export class AddStyleComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/examples/ng9/src/app/app.module.ts b/examples/ng9/src/app/app.module.ts index e3550810..85c842b3 100644 --- a/examples/ng9/src/app/app.module.ts +++ b/examples/ng9/src/app/app.module.ts @@ -10,19 +10,27 @@ import { NetworkService } from './network.service'; import { HtmlMountComponent } from './html-mount/html-mount.component'; import { OutputSubscribeComponent } from './output-subscribe/output-subscribe.component'; import { ImageSnapshotComponent } from './image-snapshot/image-snapshot.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MaterialButtonComponent } from './material-button/material-button.component'; +import { BootstrapButtonComponent } from './bootstrap-button/bootstrap-button.component'; +import { AddStyleComponent } from './add-style/add-style.component'; @NgModule({ - declarations: [ + declarations: [ AppComponent, OnPushStratComponent, NetworkComponent, HtmlMountComponent, OutputSubscribeComponent, ImageSnapshotComponent, - ], + MaterialButtonComponent, + BootstrapButtonComponent, + AddStyleComponent + ], imports: [ BrowserModule, HttpClientModule, + BrowserAnimationsModule, ], providers: [HeroService, NetworkService], bootstrap: [AppComponent] diff --git a/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.css b/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.css new file mode 100644 index 00000000..e69de29b diff --git a/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.cy-spec.ts b/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.cy-spec.ts new file mode 100644 index 00000000..76bc1b33 --- /dev/null +++ b/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.cy-spec.ts @@ -0,0 +1,17 @@ +import { initEnv, mount, setConfig } from 'cypress-angular-unit-test'; +import { BootstrapButtonComponent } from './bootstrap-button.component'; + +describe('BootstrapButtonComponent', () => { + + beforeEach(() => { + setConfig({ stylesheet: 'https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css' }); + initEnv(BootstrapButtonComponent); + }); + + it('should create', () => { + mount(BootstrapButtonComponent); + // Should have BootStrap color style + cy.get('button').should('have.css', 'background-color', 'rgb(0, 123, 255)'); + }); + +}); diff --git a/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.html b/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.html new file mode 100644 index 00000000..eb2e6d5e --- /dev/null +++ b/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.html @@ -0,0 +1 @@ + diff --git a/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.ts b/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.ts new file mode 100644 index 00000000..bb5a2499 --- /dev/null +++ b/examples/ng9/src/app/bootstrap-button/bootstrap-button.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-bootstrap-button', + templateUrl: './bootstrap-button.component.html', + styleUrls: ['./bootstrap-button.component.css'] +}) +export class BootstrapButtonComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/examples/ng9/src/app/material-button/material-button.component.css b/examples/ng9/src/app/material-button/material-button.component.css new file mode 100644 index 00000000..e69de29b diff --git a/examples/ng9/src/app/material-button/material-button.component.cy-spec.ts b/examples/ng9/src/app/material-button/material-button.component.cy-spec.ts new file mode 100644 index 00000000..1758c670 --- /dev/null +++ b/examples/ng9/src/app/material-button/material-button.component.cy-spec.ts @@ -0,0 +1,16 @@ +import { initEnv, mount, setConfig } from 'cypress-angular-unit-test'; +import { MaterialButtonComponent } from './material-button.component'; +import { MatButtonModule } from '@angular/material/button'; + +describe('MaterialButtonComponent', () => { + + beforeEach(() => { + setConfig({ cssFile: 'node_modules/@angular/material/prebuilt-themes/indigo-pink.css' }); + initEnv(MaterialButtonComponent, { imports: [MatButtonModule] }); + }); + + it.skip('should create', () => { + mount(MaterialButtonComponent); + }); + +}); diff --git a/examples/ng9/src/app/material-button/material-button.component.html b/examples/ng9/src/app/material-button/material-button.component.html new file mode 100644 index 00000000..7fe272b7 --- /dev/null +++ b/examples/ng9/src/app/material-button/material-button.component.html @@ -0,0 +1,2 @@ +

material-button works!

+ \ No newline at end of file diff --git a/examples/ng9/src/app/material-button/material-button.component.ts b/examples/ng9/src/app/material-button/material-button.component.ts new file mode 100644 index 00000000..ec92c4fd --- /dev/null +++ b/examples/ng9/src/app/material-button/material-button.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit } from '@angular/core'; +import '@angular/material/prebuilt-themes/indigo-pink.css'; + +@Component({ + selector: 'app-material-button', + templateUrl: './material-button.component.html', + styleUrls: ['./material-button.component.css'] +}) +export class MaterialButtonComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/examples/ng9/src/app/work.cy-spec.ts b/examples/ng9/src/app/work.cy-spec.ts index 879d1320..9e6d3db8 100644 --- a/examples/ng9/src/app/work.cy-spec.ts +++ b/examples/ng9/src/app/work.cy-spec.ts @@ -73,7 +73,6 @@ describe('AppComponent', () => { // component + any inputs object const fixture = mountt(AppComponent, { title: 'World' }); - console.log(fixture); // use any Cypress command afterwards cy.contains('World app is running!'); @@ -88,7 +87,6 @@ describe('AppComponent', () => { // component + any inputs object const fixture = mountt(AppComponent, { title: 'World' }); - console.log(fixture); // use any Cypress command afterwards cy.contains('World app is running!'); diff --git a/examples/ng9/src/index.html b/examples/ng9/src/index.html index 2e7d4ea5..44ddef6a 100644 --- a/examples/ng9/src/index.html +++ b/examples/ng9/src/index.html @@ -6,8 +6,10 @@ + + - + diff --git a/examples/ng9/src/styles.css b/examples/ng9/src/styles.css index 90d4ee00..7e7239a2 100644 --- a/examples/ng9/src/styles.css +++ b/examples/ng9/src/styles.css @@ -1 +1,4 @@ /* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } diff --git a/lib/config.ts b/lib/config.ts index 6078a689..5bdabec3 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1,5 +1,46 @@ -export class CypressAngularConfig { +export class StyleOptions { - detectChanges = true; + /** + * Creates element for each stylesheet + * @alias stylesheet + */ + stylesheets?: string[]; + /** + * Creates element for each stylesheet + * @alias stylesheets + */ + stylesheet?: string; + + /** + * Creates element and inserts given CSS. + * @alias styles + */ + style?: string; + + /** + * Creates element for each given CSS text. + * @alias style + */ + styles?: string[]; + + /** + * Loads each file and creates a element + * with the loaded CSS + * @alias cssFile + */ + cssFiles?: string[]; + + /** + * Single CSS file to load into a element + * @alias cssFile + */ + cssFile?: string; + +} +export class CypressAngularConfig extends StyleOptions { + + detectChanges?= true; + + log?= false; } \ No newline at end of file diff --git a/lib/css-utils.ts b/lib/css-utils.ts new file mode 100644 index 00000000..97e0afca --- /dev/null +++ b/lib/css-utils.ts @@ -0,0 +1,118 @@ +import { CypressAngularConfig } from './config'; + +/** + * Insert links to external style resources. + */ +function insertStylesheets( + stylesheets: string[], + document: Document, + el: HTMLElement, +) { + stylesheets.forEach(href => { + const link = document.createElement('link') + link.type = 'text/css' + link.rel = 'stylesheet' + link.href = href + document.body.insertBefore(link, el); + }) +} + +/** + * Inserts a single stylesheet element + */ +function insertStyles(styles: string[], document: Document, el: HTMLElement) { + styles.forEach(style => { + const styleElement = document.createElement('style') + styleElement.appendChild(document.createTextNode(style)) + document.body.insertBefore(styleElement, el) + }) +} + +function insertSingleCssFile( + cssFilename: string, + document: Document, + el: HTMLElement, + log?: boolean, +) { + cy.readFile(cssFilename, { log }).then(css => { + const style = document.createElement('style') + style.appendChild(document.createTextNode(css)) + document.body.insertBefore(style, el) + }) +} + +/** + * Reads the given CSS file from local file system + * and adds the loaded style text as an element. + */ +function insertLocalCssFiles( + cssFilenames: string[], + document: Document, + el: HTMLElement, + log?: boolean, +) { + cssFilenames.forEach(cssFilename => + insertSingleCssFile(cssFilename, document, el, log) + ); +} + +/** + * Injects custom style text or CSS file or 3rd party style resources + * into the given document. + */ +export function injectStylesBeforeElement( + options: CypressAngularConfig, + document: Document, + el: HTMLElement, +): void { + // first insert all stylesheets as Link elements + let stylesheets: string[] = [] + + if (typeof options.stylesheet === 'string') { + stylesheets.push(options.stylesheet) + } else if (Array.isArray(options.stylesheet)) { + stylesheets = stylesheets.concat(options.stylesheet) + } + + if (typeof options.stylesheets === 'string') { + options.stylesheets = [options.stylesheets] + } + if (options.stylesheets) { + stylesheets = stylesheets.concat(options.stylesheets) + } + + insertStylesheets(stylesheets, document, el) + + // insert any styles as elements + let styles: string[] = [] + if (typeof options.style === 'string') { + styles.push(options.style) + } else if (Array.isArray(options.style)) { + styles = styles.concat(options.style) + } + if (typeof options.styles === 'string') { + styles.push(options.styles) + } else if (Array.isArray(options.styles)) { + styles = styles.concat(options.styles) + } + + insertStyles(styles, document, el) + + // now load any css files by path and add their content + // as elements + let cssFiles: string[] = [] + + if (typeof options.cssFile === 'string') { + cssFiles.push(options.cssFile) + } else if (Array.isArray(options.cssFile)) { + cssFiles = cssFiles.concat(options.cssFile) + } + + if (typeof options.cssFiles === 'string') { + cssFiles.push(options.cssFiles) + } else if (Array.isArray(options.cssFiles)) { + cssFiles = cssFiles.concat(options.cssFiles) + } + + insertLocalCssFiles(cssFiles, document, el, options.log) +} \ No newline at end of file diff --git a/lib/index.ts b/lib/index.ts index 449bb5e8..6233a406 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -3,6 +3,7 @@ import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@ang import { ProxyComponent } from './proxy.component'; import { CypressAngularConfig } from './config'; import { Type } from '@angular/core'; +import { injectStylesBeforeElement } from './css-utils'; let config = new CypressAngularConfig(); @@ -39,6 +40,14 @@ export function initEnv(component: any, moduleDef?: TestModuleMetadata): void { imports: moduleDef ? moduleDef.imports : [], providers }).compileComponents(); + + // @ts-ignore + const document: Document = cy.state('document'); + const el = document.getElementById('root'); + if (el === null) { + throw new Error("root element not found"); + } + injectStylesBeforeElement(config, document, el); }; export function mount(component: Type, inputs?: object): ComponentFixture { diff --git a/support.js b/support.js index 201cba25..d2c1e359 100644 --- a/support.js +++ b/support.js @@ -24,7 +24,7 @@ beforeEach(() => { - + `; const document = cy.state('document');