Skip to content

Commit 3b92e6c

Browse files
committed
feat: initial functionality
1 parent b7f34ea commit 3b92e6c

13 files changed

+6837
-0
lines changed

.npmignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
coverage/
2+
.nyc_output/
3+
test/
4+

.travis.yml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
dist: trusty
2+
language: node_js
3+
cache: yarn
4+
notifications:
5+
email: false
6+
node_js:
7+
- v9
8+
- v8
9+
- v6
10+
before_install:
11+
- npm install -g yarn coveralls nyc
12+
script:
13+
- yarn rebuild
14+
- yarn test:lint
15+
- yarn test:unit --coverage --runInBand --verbose
16+
after_success:
17+
- cat coverage/lcov.info | coveralls || echo 'Failed to upload to coveralls...'
18+
- yarn semantic-release

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2018 Patrick Hulce <[email protected]> (https://patrickhulce.com/)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

jest.config.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
collectCoverageFrom: ['**/*.ts', '!**/*.d.ts'],
3+
transform: {
4+
'\\.ts$': 'ts-jest',
5+
},
6+
moduleFileExtensions: ['ts', 'js', 'json'],
7+
testMatch: ['**/*.test.ts'],
8+
}

lib/extend.ts

Whitespace-only changes.

lib/index.ts

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import * as path from 'path'
2+
import {readFileSync} from 'fs'
3+
import {MatcherOptions, Matcher, SelectorMatcherOptions} from 'dom-testing-library/typings/'
4+
import {ElementHandle, EvaluateFn} from 'puppeteer'
5+
6+
const Page = require('puppeteer/lib/Page.js')
7+
const ElementHandle = require('puppeteer/lib/ElementHandle.js')
8+
9+
const domLibraryAsString = readFileSync(
10+
path.join(__dirname, '../dom-testing-library.js'),
11+
'utf8',
12+
).replace(/process.env/g, '{}')
13+
14+
const mockFnToExecuteInPage = `
15+
function evaluateInPage(container, fnName, ...args) {
16+
${domLibraryAsString}
17+
18+
const mappedArgs = args.map(item => item.regex ? new RegExp(item.regex) : item)
19+
return __dom_testing_library__[fnName](container, ...mappedArgs)
20+
}
21+
`
22+
23+
type DOMReturnType = ElementHandle | ElementHandle[] | null
24+
25+
function createDelegateFor(fnName: string): (...args: any[]) => Promise<DOMReturnType> {
26+
return async function(...args: any[]): Promise<DOMReturnType> {
27+
if (fnName.includes('All')) throw new Error('*All methods not yet supported')
28+
29+
// @ts-ignore
30+
const containerHandle: ElementHandle = this
31+
// @ts-ignore
32+
const evaluateFn: EvaluateFn = {toString: () => mockFnToExecuteInPage}
33+
34+
const mappedArgs = args.map(arg => (arg instanceof RegExp ? {regex: arg.source} : arg))
35+
const handle = await containerHandle
36+
.executionContext()
37+
.evaluateHandle(evaluateFn, containerHandle, fnName, ...mappedArgs)
38+
const element = handle.asElement()
39+
if (element) return element
40+
await handle.dispose()
41+
return null
42+
}
43+
}
44+
45+
Page.prototype.getTestingUtilsForDocument = async function(): Promise<ElementHandle> {
46+
const documentHandle = await this.mainFrame().evaluateHandle('document')
47+
return await documentHandle.asElement()
48+
}
49+
50+
ElementHandle.prototype.queryByPlaceholderText = createDelegateFor('queryByPlaceholderText')
51+
ElementHandle.prototype.queryAllByPlaceholderText = createDelegateFor('queryAllByPlaceholderText')
52+
ElementHandle.prototype.getByPlaceholderText = createDelegateFor('getByPlaceholderText')
53+
ElementHandle.prototype.getAllByPlaceholderText = createDelegateFor('getAllByPlaceholderText')
54+
ElementHandle.prototype.queryByText = createDelegateFor('queryByText')
55+
ElementHandle.prototype.queryAllByText = createDelegateFor('queryAllByText')
56+
ElementHandle.prototype.getByText = createDelegateFor('getByText')
57+
ElementHandle.prototype.getAllByText = createDelegateFor('getAllByText')
58+
ElementHandle.prototype.queryByLabelText = createDelegateFor('queryByLabelText')
59+
ElementHandle.prototype.queryAllByLabelText = createDelegateFor('queryAllByLabelText')
60+
ElementHandle.prototype.getByLabelText = createDelegateFor('getByLabelText')
61+
ElementHandle.prototype.getAllByLabelText = createDelegateFor('getAllByLabelText')
62+
ElementHandle.prototype.queryByAltText = createDelegateFor('queryByAltText')
63+
ElementHandle.prototype.queryAllByAltText = createDelegateFor('queryAllByAltText')
64+
ElementHandle.prototype.getByAltText = createDelegateFor('getByAltText')
65+
ElementHandle.prototype.getAllByAltText = createDelegateFor('getAllByAltText')
66+
ElementHandle.prototype.queryByTestId = createDelegateFor('queryByTestId')
67+
ElementHandle.prototype.queryAllByTestId = createDelegateFor('queryAllByTestId')
68+
ElementHandle.prototype.getByTestId = createDelegateFor('getByTestId')
69+
ElementHandle.prototype.getAllByTestId = createDelegateFor('getAllByTestId')
70+
ElementHandle.prototype.queryByTitle = createDelegateFor('queryByTitle')
71+
ElementHandle.prototype.queryAllByTitle = createDelegateFor('queryAllByTitle')
72+
ElementHandle.prototype.getByTitle = createDelegateFor('getByTitle')
73+
ElementHandle.prototype.getAllByTitle = createDelegateFor('getAllByTitle')
74+
75+
declare module 'puppeteer' {
76+
interface Page {
77+
getTestingUtilsForDocument(): ElementHandle
78+
}
79+
80+
interface ElementHandle {
81+
queryByPlaceholderText(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle | null>
82+
queryAllByPlaceholderText(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle[]>
83+
getByPlaceholderText(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle>
84+
getAllByPlaceholderText(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle[]>
85+
queryByText(m: Matcher, opts?: SelectorMatcherOptions): Promise<ElementHandle | null>
86+
queryAllByText(m: Matcher, opts?: SelectorMatcherOptions): Promise<ElementHandle[]>
87+
getByText(m: Matcher, opts?: SelectorMatcherOptions): Promise<ElementHandle>
88+
getAllByText(m: Matcher, opts?: SelectorMatcherOptions): Promise<ElementHandle[]>
89+
queryByLabelText(m: Matcher, opts?: SelectorMatcherOptions): Promise<ElementHandle | null>
90+
queryAllByLabelText(m: Matcher, opts?: SelectorMatcherOptions): Promise<ElementHandle[]>
91+
getByLabelText(m: Matcher, opts?: SelectorMatcherOptions): Promise<ElementHandle>
92+
getAllByLabelText(m: Matcher, opts?: SelectorMatcherOptions): Promise<ElementHandle[]>
93+
queryByAltText(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle | null>
94+
queryAllByAltText(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle[]>
95+
getByAltText(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle>
96+
getAllByAltText(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle[]>
97+
queryByTestId(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle | null>
98+
queryAllByTestId(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle[]>
99+
getByTestId(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle>
100+
getAllByTestId(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle[]>
101+
queryByTitle(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle | null>
102+
queryAllByTitle(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle[]>
103+
getByTitle(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle>
104+
getAllByTitle(m: Matcher, opts?: MatcherOptions): Promise<ElementHandle[]>
105+
}
106+
}

package.json

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "pptr-testing-library",
3+
"version": "0.0.0-development",
4+
"description": "puppeteer + dom-testing-library",
5+
"main": "./lib/index.js",
6+
"scripts": {
7+
"test": "npm run test:lint && npm run test:unit",
8+
"test:unit": "jest",
9+
"test:lint": "lint",
10+
"semantic-release": "semantic-release",
11+
"clean": "rm -fR dist/",
12+
"rebuild": "npm run clean && npm run build",
13+
"prepublish": "npm run rebuild",
14+
"build": "npm run build:ts",
15+
"build:ts": "tsc",
16+
"build:rollup": "rollup -c rollup.config.js"
17+
},
18+
"repository": {
19+
"type": "git",
20+
"url": "https://github.com/patrickhulce/pptr-testing-library.git"
21+
},
22+
"author": "Patrick Hulce <[email protected]>",
23+
"license": "MIT",
24+
"homepage": "https://github.com/patrickhulce/pptr-testing-library#readme",
25+
"bugs": {
26+
"url": "https://github.com/patrickhulce/pptr-testing-library/issues"
27+
},
28+
"devDependencies": {
29+
"@patrickhulce/lint": "^2.1.3",
30+
"@types/jest": "^23.1.1",
31+
"@types/puppeteer": "^1.3.4",
32+
"dom-testing-library": "2.6.1",
33+
"jest": "^23.1.0",
34+
"puppeteer": "^1.5.0",
35+
"rollup": "^0.61.1",
36+
"rollup-plugin-commonjs": "^9.1.3",
37+
"rollup-plugin-node-resolve": "^3.3.0",
38+
"semantic-release": "^11.0.0",
39+
"ts-jest": "^22.4.6",
40+
"tslint": "^5.10.0",
41+
"typescript": "^2.9.2"
42+
},
43+
"peerDependencies": {
44+
"puppeteer": "^1.5.0"
45+
}
46+
}

rollup.config.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const path = require('path')
2+
3+
module.exports = {
4+
input: path.join(__dirname, 'node_modules/dom-testing-library/dist/queries.js'),
5+
output: {
6+
file: 'dom-testing-library.js',
7+
format: 'iife',
8+
name: '__dom_testing_library__',
9+
},
10+
plugins: [require('rollup-plugin-node-resolve')(), require('rollup-plugin-commonjs')()],
11+
}

test/__snapshots__/index.test.ts.snap

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`lib/index.ts should handle the LabelText methods 1`] = `"<input id=\\"label-text-input\\" type=\\"text\\">"`;
4+
5+
exports[`lib/index.ts should handle the get* methods 1`] = `"<input type=\\"text\\" data-testid=\\"testid-text-input\\">"`;

test/fixtures/page.html

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html>
3+
4+
<body>
5+
<h1>Hello h1</h1>
6+
<h2>Hello h2</h2>
7+
<img alt="Image A" src="">
8+
<input type="text" data-testid="testid-text-input">
9+
<label for="label-text-input">Label A</label>
10+
<input id="label-text-input" type="text">
11+
</body>
12+
13+
</html>

test/index.test.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as path from 'path'
2+
import * as puppeteer from 'puppeteer'
3+
import '../lib'
4+
5+
describe('lib/index.ts', () => {
6+
let browser: puppeteer.Browser
7+
let page: puppeteer.Page
8+
let document: puppeteer.ElementHandle
9+
10+
it('should launch puppeteer', async () => {
11+
browser = await puppeteer.launch()
12+
page = await browser.newPage()
13+
await page.goto(`file://${path.join(__dirname, 'fixtures/page.html')}`)
14+
})
15+
16+
it('should extend puppeteer ElementHandle', async () => {
17+
document = await page.getTestingUtilsForDocument()
18+
expect(typeof document.queryAllByAltText).toBe('function')
19+
})
20+
21+
it('should handle the query* methods', async () => {
22+
const element = await document.queryByText('Hello h1')
23+
expect(element).toBeTruthy()
24+
expect(await page.evaluate(el => el.textContent, element)).toEqual('Hello h1')
25+
})
26+
27+
it('should handle regex matching', async () => {
28+
const element = await document.queryByText(/Hello/)
29+
expect(element).toBeTruthy()
30+
expect(await page.evaluate(el => el.textContent, element)).toEqual('Hello h1')
31+
})
32+
33+
it('should handle the get* methods', async () => {
34+
const element = await document.getByTestId('testid-text-input')
35+
expect(await page.evaluate(el => el.outerHTML, element)).toMatchSnapshot()
36+
})
37+
38+
it('should handle the LabelText methods', async () => {
39+
const element = await document.getByLabelText('Label A')
40+
expect(await page.evaluate(el => el.outerHTML, element)).toMatchSnapshot()
41+
})
42+
43+
it.skip('should handle the queryAll* methods', async () => {
44+
const elements = await document.queryAllByText(/Hello/)
45+
expect(elements).toHaveLength(2)
46+
47+
const text = await Promise.all([
48+
page.evaluate(el => el.textContent, elements[0]),
49+
page.evaluate(el => el.textContent, elements[1]),
50+
])
51+
52+
expect(text).toEqual(['Hello h1', 'Hello h2'])
53+
})
54+
55+
afterAll(async () => {
56+
await browser.close()
57+
})
58+
})

tsconfig.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compileOnSave": true,
3+
"compilerOptions": {
4+
"outDir": "./dist",
5+
"target": "es2015",
6+
"module": "commonjs",
7+
"moduleResolution": "node",
8+
"sourceMap": true,
9+
"declaration": true,
10+
11+
"strict": true,
12+
"noImplicitAny": true,
13+
"noImplicitThis": true,
14+
"strictNullChecks": true,
15+
"preserveWatchOutput": true,
16+
},
17+
"include": [
18+
"lib/**/*"
19+
]
20+
}

0 commit comments

Comments
 (0)