Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(iframe): add redirections for appendChild/querySelector/querySelectorAll to use main application iframe #15

Merged
merged 2 commits into from
Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
This is a library designed to feel like the official [aurelia/testing](https://github.com/aurelia/testing) library but instead it is built to work with Cypress.
The purpose of this module is so that components can be visually unit tested as close to reality as possible, ie. in a browser rather than in NodeJS with a virtual DOM.

## Known Limitations

- `viewModel` is `undefined` in Cypress if a component has been annotated with @containerless.
- Appending elements directly to the `document.body` may not work as expected due to the fact that Cypress sandboxes it's test code and application code into seperate iframes as it was not currently designed for you to import components directly. To work around this limitation there is currently some code in place which overrides the functionality of the following methods to redirect calls to the main application iframe.
- `document.addEventListener`
- This was added to fix `click.delegate` so that click events were processed as expected.
- `document.appendChild`
- `document.querySelector`
- `document.querySelectorAll`
- `document.body.appendChild`
- `document.body.querySelector`
- `document.body.querySelectorAll`

## Credits

- [Gleb Bahmutov](https://github.com/bahmutov/cypress-react-unit-test) for their Cypress React Unit Test library. It helped me figure out how to get styles working quickly.
43 changes: 43 additions & 0 deletions cypress/integration/unit/iframe-fixes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { PLATFORM } from 'aurelia-framework';
import { bootstrap } from 'aurelia-bootstrapper';
import { ComponentTester, StageComponent } from 'cypress-aurelia-unit-test';

import { configurationWithoutStart } from '~/main';
import { DocumentManipulator } from '~/document-manipulator/document-manipulator';

describe('IframeFixes', () => {
let component: ComponentTester<DocumentManipulator>;

beforeEach(() => {
component = StageComponent
.withResources(PLATFORM.moduleName('document-manipulator/document-manipulator'))
.inView(`
<document-manipulator></document-manipulator>`)
.bootstrap(configurationWithoutStart);
});

it('Check document.body.appendChild override is active', () => {
component.create(bootstrap);
cy.get('.bodyAppendChild').should('exist');
});

it('Check document.querySelector override is active', () => {
component.create(bootstrap);
cy.get('.documentQuerySelectorSuccess').should('exist');
});

it('Check document.body.querySelector override is active', () => {
component.create(bootstrap);
cy.get('.documentBodyQuerySelectorSuccess').should('exist');
});

it('Check document.querySelectorAll override is active', () => {
component.create(bootstrap);
cy.get('.documentQueryAllSelectorSuccess').should('exist');
});

it('Check document.body.querySelectorAll override is active', () => {
component.create(bootstrap);
cy.get('.documentBodyQueryAllSelectorSuccess').should('exist');
});
});
4 changes: 2 additions & 2 deletions lib/component-tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const aureliaDialogWarningMessage = 'aurelia-dialog causes Cypress to crash. Rem

// having weak reference to styles prevents garbage collection
// and "losing" styles when the next test starts
const stylesCache: Map<string, NodeListOf<Element>> = new Map<string, NodeListOf<Element>>();
const stylesCache: Map<string, NodeListOf<HTMLStyleElement>> = new Map<string, NodeListOf<HTMLStyleElement>>();

// NOTE: Jake: 2018-12-18
// Taken from: https://github.com/bahmutov/cypress-react-unit-test/blob/master/lib/index.js
Expand All @@ -45,7 +45,7 @@ function copyStyles(componentName: string): void {
// like component name
const hash = componentName;

let styles = document.querySelectorAll('head style');
let styles = document.head.querySelectorAll('style');
if (styles.length) {
// tslint:disable-next-line:no-console
console.log('Injected %d styles into view', styles.length);
Expand Down
39 changes: 0 additions & 39 deletions lib/event-listener-fixes.ts

This file was deleted.

102 changes: 102 additions & 0 deletions lib/iframe-fixes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
function overrideSpecIFrameToApplyToAppIFrame(): void {
const specIframe = window.parent.document.querySelector('iframe.spec-iframe');
if (!specIframe) {
throw new Error('Cannot find .spec-iframe');
}
const specDocument: Document = (specIframe as any).contentDocument;
if (!specDocument) {
throw new Error('Cannot find contentDocument on specIframe');
}

const rootDocument = window.parent.document;
const appIframe = rootDocument.querySelector('iframe.aut-iframe');
if (!appIframe) {
throw new Error('Cannot find .aut-iframe');
}
const appDocument: Document = (appIframe as any).contentDocument;
if (!appDocument) {
throw new Error('Cannot find contentDocument on appIframe');
}

// Override addEventListener on spec iframe so that we can attach those
// same events to the app iframe, this fixes:
// - aurelia-binding - click.delegate won't work in templates without this.
{
const originalEventListener = specDocument.addEventListener;
const addEventListener = function(this: any): void {
originalEventListener.apply(this, arguments as any);
appDocument.addEventListener.apply(appDocument, arguments as any);
};
if (specDocument.addEventListener !== addEventListener) {
specDocument.addEventListener = addEventListener;
}
}

// Override document.appendChild on spec iframe so that we can append those
// elements to the app iframe.
{
const documentAppendChild = function <T extends Node>(this: T): T {
return appDocument.appendChild.apply(appDocument, arguments as any) as T;
};
if (specDocument.appendChild !== documentAppendChild) {
specDocument.appendChild = documentAppendChild;
}
}

// Override document.body.appendChild on spec iframe so that we can append those
// elements to the app iframe.
{
const bodyAppendChild = function <T extends Node>(this: T): T {
return appDocument.body.appendChild.apply(appDocument.body, arguments as any) as T;
};
if (specDocument.body.appendChild !== bodyAppendChild) {
specDocument.body.appendChild = bodyAppendChild;
}
}

// Override document.querySelector on spec iframe so that we can query for
// elements that have been rendered in the app iframe.
{
const documentQuerySelector = function(this: any): Element | null {
return appDocument.querySelector.apply(appDocument, arguments as any);
};
if (specDocument.querySelector !== documentQuerySelector) {
specDocument.querySelector = documentQuerySelector;
}
}

// Override document.body.querySelector on spec iframe so that we can query for
// elements that have been rendered in the app iframe.
{
const bodyQuerySelector = function(this: any): Element | null {
return appDocument.body.querySelector.apply(appDocument.body, arguments as any);
};
if (specDocument.body.querySelector !== bodyQuerySelector) {
specDocument.body.querySelector = bodyQuerySelector;
}
}

// Override document.querySelectorAll on spec iframe so that we can query for
// elements that have been rendered in the app iframe.
{
const documentQueryAllSelector = function(this: any): NodeListOf<Element> {
return appDocument.querySelectorAll.apply(appDocument, arguments as any);
};
if (specDocument.querySelectorAll !== documentQueryAllSelector) {
specDocument.querySelectorAll = documentQueryAllSelector;
}
}

// Override document.querySelectorAll on spec iframe so that we can query for
// elements that have been rendered in the app iframe.
{
const bodyQueryAllSelector = function(this: any): NodeListOf<Element> {
return appDocument.body.querySelectorAll.apply(appDocument.body, arguments as any);
};
if (specDocument.body.querySelectorAll !== bodyQueryAllSelector) {
specDocument.body.querySelectorAll = bodyQueryAllSelector;
}
}
}

before(overrideSpecIFrameToApplyToAppIFrame);
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import './event-listener-fixes';
import './iframe-fixes';
export * from './component-tester';
29 changes: 29 additions & 0 deletions src/document-manipulator/document-manipulator.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<div>Document Manipulator</div>
<div>This component exists to test if various "document" functions have been overridden to point from the Spec IFrame to the App IFrame.</div>
<hr/>
<div
if.bind="documentQuerySelectorSuccess"
class="documentQuerySelectorSuccess"
>
- documentQuerySelector
</div>
<div
if.bind="documentBodyQuerySelectorSuccess"
class="documentBodyQuerySelectorSuccess"
>
- documentBodyQuerySelector
</div>
<div
if.bind="documentQueryAllSelectorSuccess"
class="documentQueryAllSelectorSuccess"
>
- documentQueryAllSelector
</div>
<div
if.bind="documentBodyQueryAllSelectorSuccess"
class="documentBodyQueryAllSelectorSuccess"
>
- documentBodyQueryAllSelector
</div>
</template>
47 changes: 47 additions & 0 deletions src/document-manipulator/document-manipulator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { autoinject } from 'aurelia-framework';

@autoinject
export class DocumentManipulator {
protected documentQuerySelectorSuccess: boolean = false;

protected documentBodyQuerySelectorSuccess: boolean = false;

protected documentQueryAllSelectorSuccess: boolean = false;

protected documentBodyQueryAllSelectorSuccess: boolean = false;

protected attached(): void {
// NOTE(Jake): 2019-02-20
// We create a bunch of elements that manipulate 'document'
// directly. This is to test that our code in 'iframe-fixes.ts' works.

{
const el = document.createElement('div');
el.classList.add('bodyAppendChild');
el.textContent = '- bodyAppendChild';
document.body.appendChild(el);
}

{
const el = document.querySelector('document-manipulator');
if (el) {
this.documentQuerySelectorSuccess = true;
}
}

{
const el = document.body.querySelector('document-manipulator');
if (el) {
this.documentBodyQuerySelectorSuccess = true;
}
}

if (document.querySelectorAll('document-manipulator').length > 0) {
this.documentQueryAllSelectorSuccess = true;
}

if (document.body.querySelectorAll('document-manipulator').length > 0) {
this.documentBodyQueryAllSelectorSuccess = true;
}
}
}