diff --git a/package.json b/package.json index 826de6bf..f10f5ec9 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "repository": "percy/percy-agent", "scripts": { "build": "npm run clean && rm -rf dist && tsc && npm run build-client", - "build-client": "mkdir -p dist/public && browserify src/percy-agent-client/*.ts -p [ tsify --noImplicitAny ] --standalone Percy > dist/public/percy-agent.js", + "build-client": "mkdir -p dist/public && browserify src/percy-agent-client/index.ts -p [ tsify --noImplicitAny ] --standalone PercyAgent > dist/public/percy-agent.js", "build-client-test": "npm run build-client && browserify test/percy-agent-client/*.ts -p [ tsify --noImplicitAny ] > dist-test/browserified-tests.js", "clean": "rm -f .oclif.manifest.json", "lint": "tsc -p test --noEmit && tslint -p test -t stylish --fix", diff --git a/src/index.ts b/src/index.ts index 4caa481e..3a945f74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,3 @@ export {run} from '@oclif/command' + +module.exports = require('./percy-agent-client/percy-agent').default diff --git a/src/percy-agent-client/index.ts b/src/percy-agent-client/index.ts new file mode 100644 index 00000000..fde41942 --- /dev/null +++ b/src/percy-agent-client/index.ts @@ -0,0 +1 @@ +module.exports = require('./percy-agent').default diff --git a/src/percy-agent-client/percy-agent-client.ts b/src/percy-agent-client/percy-agent-client.ts index 93bd7a42..1595084c 100644 --- a/src/percy-agent-client/percy-agent-client.ts +++ b/src/percy-agent-client/percy-agent-client.ts @@ -1,4 +1,4 @@ -class PercyAgent { +export class PercyAgentClient { xhr: XMLHttpRequest constructor(xhr?: any) { @@ -11,95 +11,3 @@ class PercyAgent { this.xhr.send(JSON.stringify(data)) } } - -export interface SnapshotOptions { - enableJavascript?: boolean, - widths?: number[], - minimumHeight?: number, - document?: Document, -} - -export class PercyAgentClient { - userAgent: string | null - xhr: any - port: number - domTransformation: any | null - readonly defaultDoctype = '' - - // TODO: make it so options here can be passed in any order - constructor(userAgent?: string, xhr?: any, domTransformation?: any, port?: number) { - this.userAgent = userAgent || null - this.xhr = xhr || XMLHttpRequest - this.domTransformation = domTransformation || null - this.port = port || 5338 - } - - snapshot(name: string, options: SnapshotOptions = {}) { - let documentObject = options.document || document - let domSnapshot = this.domSnapshot(documentObject) - let percyAgent = new PercyAgent(this.xhr) - - percyAgent.post(`http://localhost:${this.port}/percy/snapshot`, { - name, - url: documentObject.URL, - enableJavascript: options.enableJavascript, - widths: options.widths, - clientUserAgent: this.userAgent, - domSnapshot - }) - } - - private domSnapshot(documentObject: Document): string { - let doctype = this.getDoctype(documentObject) - let dom = this.stabalizeDOM(documentObject.documentElement) - let domClone = dom.cloneNode(true) as HTMLElement - - // Sometimes you'll want to transform the DOM provided into one ready for snapshotting - // For example, if your test suite runs tests in an element inside a page that - // lists all yours tests. You'll want to "hoist" the contents of the testing container to be - // the full page. Using a dom transformation is how you'd acheive that. - if (this.domTransformation) { - domClone = this.domTransformation(domClone) - } - - return doctype + domClone.outerHTML - } - - private getDoctype(documentObject: Document): string { - return documentObject.doctype ? this.doctypeToString(documentObject.doctype) : this.defaultDoctype - } - - private doctypeToString(doctype: DocumentType): string { - const publicDeclaration = doctype.publicId ? ` PUBLIC "${doctype.publicId}" ` : '' - const systemDeclaration = doctype.systemId ? ` SYSTEM "${doctype.systemId}" ` : '' - - return `' - } - - private serializeInputElements(domClone: HTMLElement): HTMLElement { - const inputNodes = domClone.getElementsByTagName('input') - let inputElements = Array.prototype.slice.call(inputNodes) as HTMLInputElement[] - - inputElements.forEach((elem: HTMLInputElement) => { - switch (elem.type) { - case 'checkbox': - case 'radio': - if (elem.checked) { - elem.setAttribute('checked', '') - } - break - default: - elem.setAttribute('value', elem.value) - } - }) - - return domClone - } - - private stabalizeDOM(dom: HTMLElement) { - let stabilizedDOM = this.serializeInputElements(dom) - // more calls to come here - - return stabilizedDOM - } -} diff --git a/src/percy-agent-client/percy-agent.ts b/src/percy-agent-client/percy-agent.ts new file mode 100644 index 00000000..3e983eea --- /dev/null +++ b/src/percy-agent-client/percy-agent.ts @@ -0,0 +1,87 @@ +import {PercyAgentClient} from './percy-agent-client' +import {SnapshotOptions} from './snapshot-options' + +export default class PercyAgent { + userAgent: string | null + xhr: any + port: number + domTransformation: any | null + readonly defaultDoctype = '' + + // TODO: make it so options here can be passed in any order + constructor(userAgent?: string, xhr?: any, domTransformation?: any, port?: number) { + this.userAgent = userAgent || null + this.xhr = xhr || XMLHttpRequest + this.domTransformation = domTransformation || null + this.port = port || 5338 + } + + snapshot(name: string, options: SnapshotOptions = {}) { + let documentObject = options.document || document + let domSnapshot = this.domSnapshot(documentObject) + let percyAgentClient = new PercyAgentClient(this.xhr) + + percyAgentClient.post(`http://localhost:${this.port}/percy/snapshot`, { + name, + url: documentObject.URL, + enableJavascript: options.enableJavascript, + widths: options.widths, + clientUserAgent: this.userAgent, + domSnapshot + }) + } + + private domSnapshot(documentObject: Document): string { + let doctype = this.getDoctype(documentObject) + let dom = this.stabalizeDOM(documentObject.documentElement) + let domClone = dom.cloneNode(true) as HTMLElement + + // Sometimes you'll want to transform the DOM provided into one ready for snapshotting + // For example, if your test suite runs tests in an element inside a page that + // lists all yours tests. You'll want to "hoist" the contents of the testing container to be + // the full page. Using a dom transformation is how you'd acheive that. + if (this.domTransformation) { + domClone = this.domTransformation(domClone) + } + + return doctype + domClone.outerHTML + } + + private getDoctype(documentObject: Document): string { + return documentObject.doctype ? this.doctypeToString(documentObject.doctype) : this.defaultDoctype + } + + private doctypeToString(doctype: DocumentType): string { + const publicDeclaration = doctype.publicId ? ` PUBLIC "${doctype.publicId}" ` : '' + const systemDeclaration = doctype.systemId ? ` SYSTEM "${doctype.systemId}" ` : '' + + return `' + } + + private serializeInputElements(domClone: HTMLElement): HTMLElement { + const inputNodes = domClone.getElementsByTagName('input') + let inputElements = Array.prototype.slice.call(inputNodes) as HTMLInputElement[] + + inputElements.forEach((elem: HTMLInputElement) => { + switch (elem.type) { + case 'checkbox': + case 'radio': + if (elem.checked) { + elem.setAttribute('checked', '') + } + break + default: + elem.setAttribute('value', elem.value) + } + }) + + return domClone + } + + private stabalizeDOM(dom: HTMLElement) { + let stabilizedDOM = this.serializeInputElements(dom) + // more calls to come here + + return stabilizedDOM + } +} diff --git a/src/percy-agent-client/snapshot-options.ts b/src/percy-agent-client/snapshot-options.ts new file mode 100644 index 00000000..a3f88f7b --- /dev/null +++ b/src/percy-agent-client/snapshot-options.ts @@ -0,0 +1,6 @@ +export interface SnapshotOptions { + enableJavascript?: boolean, + widths?: number[], + minimumHeight?: number, + document?: Document, +} diff --git a/test/percy-agent-client/percy-agent-client.test.ts b/test/percy-agent-client/percy-agent.test.ts similarity index 88% rename from test/percy-agent-client/percy-agent-client.test.ts rename to test/percy-agent-client/percy-agent.test.ts index 212494b5..91e46781 100644 --- a/test/percy-agent-client/percy-agent-client.test.ts +++ b/test/percy-agent-client/percy-agent.test.ts @@ -1,11 +1,11 @@ import {expect} from 'chai' -import {PercyAgentClient} from '../../src/percy-agent-client/percy-agent-client' -require('../../src/percy-agent-client/percy-agent-client') +import PercyAgent from '../../src/percy-agent-client/percy-agent' +require('../../src/percy-agent-client/percy-agent') import * as sinon from 'sinon' -describe('PercyAgentClient', () => { +describe('PercyAgent', () => { let xhr = sinon.useFakeXMLHttpRequest() - const subject = new PercyAgentClient('Test Client', xhr) + const subject = new PercyAgent('Test Client', xhr) let requests: sinon.SinonFakeXMLHttpRequest[] = [] beforeEach(() => {