Skip to content

Commit 3e703a2

Browse files
authored
feat: within using any testcafe selector (#35)
* selectors, some within * attempting to use customMethods to implement within * working w/out serialization * working w/out serialization * working with nested, still have to use an eval tho * removed commented out code
1 parent 4ad1c42 commit 3e703a2

File tree

3 files changed

+55
-94
lines changed

3 files changed

+55
-94
lines changed

src/index.js

Lines changed: 30 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -21,102 +21,44 @@ export function configure(options) {
2121
`;
2222
return { content: configFunction };
2323
}
24+
const withinSelectors = {};
25+
Object.keys(queries).forEach(withinQueryName => {
26+
27+
withinSelectors[withinQueryName] =
28+
new Function(`
29+
const els = arguments[0];
30+
if(els.length > 1) {
31+
throw new Error("within() only works with a single element, found " + els.length);
32+
}
33+
const el = els[0];
34+
const args = Array.from(arguments).slice(1);
35+
return window.TestingLibraryDom.within(el).${withinQueryName}.apply(null, args);
36+
`)
2437

38+
});
2539

2640
Object.keys(queries).forEach(queryName => {
41+
2742
module.exports[queryName] = Selector(
28-
new Function(
29-
`
30-
if(!window.tctlReplacer) {
31-
window.tctlReplacer = function tctlReplacer(key, value) {
32-
if (value instanceof RegExp)
33-
return ("__REGEXP " + value.toString());
34-
else if (typeof value === 'function')
35-
return ("__FUNCTION " + value.toString());
36-
else
37-
return value;
38-
}
39-
}
40-
const els = TestingLibraryDom.${queryName}(document.body, ...arguments);
41-
if(!Array.isArray(els)) {
42-
els.setAttribute('data-tctl-args', JSON.stringify(Array.from(arguments), window.tctlReplacer, 0));
43-
els.setAttribute('data-tctl-queryname', '${queryName}');
44-
} else {
45-
els.forEach((el,i) => {
46-
el.setAttribute('data-tctl-args', JSON.stringify(Array.from(arguments), window.tctlReplacer, 0));
47-
el.setAttribute('data-tctl-queryname', '${queryName}');
48-
el.setAttribute('data-tctl-index', i);
49-
});
50-
}
51-
return els;
52-
`,
53-
),
54-
);
43+
(...args) => window.TestingLibraryDom[queryName](document.body, ...args)
44+
, { dependencies: { queryName } });
45+
5546
})
56-
function reviver(key, value) {
57-
if (value.toString().indexOf('__REGEXP ') == 0) {
58-
const m = value.split('__REGEXP ')[1].match(/\/(.*)\/(.*)?/);
59-
return new RegExp(m[1], m[2] || '');
60-
} else
61-
return value;
62-
}
6347

64-
export const within = async selector => {
65-
if (selector instanceof Function) {
66-
return within(selector());
48+
export const within = sel => {
49+
if (sel instanceof Function) {
50+
return within(sel());
6751
}
68-
69-
if (selector.constructor.name === SELECTOR_TYPE) {
70-
const count = await selector.count;
71-
if (count > 1) {
72-
throw new Error(`within() requires a single element, found ${count}`);
73-
}
74-
const el = await selector;
75-
const withinQueryName = el.getAttribute('data-tctl-queryname');
76-
77-
const withinArgs = JSON.parse(el.getAttribute('data-tctl-args'), reviver)
78-
.map(arg => {
79-
if (arg instanceof RegExp) {
80-
return arg.toString();
81-
} else if (arg.toString().indexOf('__FUNCTION ') == 0) {
82-
return (arg.replace('__FUNCTION ', ''))
83-
} else {
84-
return JSON.stringify(arg);
85-
}
86-
}).join(', ');
87-
88-
const withinIndexer = el.hasAttribute('data-tctl-index') ? `[${el.getAttribute('data-tctl-index')}]` : '';
89-
90-
const withinSelectors = {};
91-
Object.keys(queries).forEach(queryName => {
92-
withinSelectors[queryName] = Selector(
93-
new Function(`
94-
95-
const {within, ${withinQueryName}} = TestingLibraryDom;
96-
const el = ${withinQueryName}(document.body, ${withinArgs})${withinIndexer};
97-
return within(el).${queryName}(...arguments);
98-
`
99-
));
100-
});
101-
return withinSelectors;
102-
} else if (typeof (selector) === 'string') {
103-
const sanitizedSelector = selector.replace(/"/g, "'");
104-
105-
const withinSelectors = {};
106-
107-
Object.keys(queries).forEach(queryName => {
108-
withinSelectors[queryName] = Selector(
109-
new Function(
110-
`
111-
const {within} = TestingLibraryDom;
112-
return within(document.querySelector("${sanitizedSelector}")).${queryName}(...arguments);
113-
`),
114-
)
115-
})
116-
117-
return withinSelectors;
52+
if (isSelector(sel)) {
53+
return (sel).addCustomMethods(withinSelectors, { returnDOMNodes: true })
54+
} else if (typeof (sel) === 'string') {
55+
return within(Selector(sel));
11856
} else {
119-
throw new Error(`"within" only accepts a string or another testing-library query as a parameter. ${selector} is not one of those`)
57+
throw new Error(`"within" only accepts a query (getBy, queryBy, etc), string or testcafe Selector`)
12058
}
12159
}
12260

61+
function isSelector(sel) {
62+
return sel.constructor.name === SELECTOR_TYPE;
63+
}
64+

test-app/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ <h3>getByText within</h3>
3939
<div id="nested2" data-testid="nested2">
4040
<h3>getByText within</h3>
4141
<button onclick="this.innerText = 'Button Clicked'">Button Text</button>
42+
<span>text only in 2nd nested</span>
43+
<span>another thing only in 2nd nested</span>
4244
</div>
4345
</section>
4446
<section>

tests/testcafe/within.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,18 @@ test('still works after browser page reload', async t => {
3939

4040

4141
test('works with nested selectors', async t => {
42-
const nested = await within(getByTestId('nested'));
43-
await t.expect(nested.getByText('Button Text').exists).ok()
44-
42+
await t.expect(within(getByTestId('nested')).getByText('Button Text').exists).ok();
4543
});
4644

4745
test('works with nested selector from "All" query with index - regex', async t => {
4846
const nestedDivs = getAllByTestId(/nested/);
4947
await t.expect(nestedDivs.count).eql(2);
50-
const nested = await within(nestedDivs.nth(0));
48+
const nested = within(nestedDivs.nth(1));
49+
50+
await t
51+
.expect(nested.getByText('Button Text').exists).ok()
52+
.expect(nested.getByText('text only in 2nd nested').exists).ok()
5153

52-
await t.expect(nested.getByText('Button Text').exists).ok();
5354
});
5455

5556
test('works with nested selector from "All" query with index - exact:false', async t => {
@@ -71,14 +72,30 @@ test('works with nested selector from "All" query with index - function', async
7172
await t.expect(nested.getByText('Button Text').exists).ok();
7273
});
7374

75+
test('works on a standard testcafe nested selector', async (t) => {
76+
const nested = Selector('#nested');
77+
78+
await t.expect(within(nested).getByText('Button Text').exists).ok()
79+
});
80+
81+
test('should throw if invalid param', async t => {
82+
let didThrow = false;
83+
try {
84+
await t.expect(within({ 'foo': 'bar' }).getByText('baz').exists).ok();
85+
86+
} catch (e) {
87+
didThrow = true;
88+
}
89+
await t.expect(didThrow).ok();
90+
});
7491

7592
test('should throw error if count > 1', async t => {
7693
const nestedDivs = getAllByTestId(/nested/);
7794

7895
await t.expect(nestedDivs.count).eql(2);
7996
let didThrow = false;
8097
try {
81-
await within(nestedDivs);
98+
await t.expect(within(nestedDivs).getByText('blah'));
8299
} catch (e) {
83100
didThrow = true;
84101
}

0 commit comments

Comments
 (0)