Skip to content

Commit 989b53e

Browse files
committed
feat: add remove subroutine
1 parent ed6a26f commit 989b53e

File tree

9 files changed

+166
-4
lines changed

9 files changed

+166
-4
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Have you got suggestions for improvement? [I am all ears](https://github.com/gaj
6060
* [`nextUntil` subroutine](#nextuntil-subroutine)
6161
* [`select` subroutine](#select-subroutine)
6262
* [Quantifier expression](#quantifier-expression)
63+
* [`remove` subroutine](#remove-subroutine)
6364
* [`read` subroutine](#read-subroutine)
6465
* [`test` subroutine](#test-subroutine)
6566
* [User-defined subroutines](#user-defined-subroutines)
@@ -229,6 +230,27 @@ x('select .foo {0,}[0]');
229230

230231
```
231232

233+
#### `remove` subroutine
234+
235+
`remove` subroutine is used to remove elements from the document using an [evaluator](#evaluators).
236+
237+
`remove` subroutine accepts the same parameters as the `select` subroutine.
238+
239+
The result of `remove` subroutine is the input of the subroutine, i.e. previous `select` subroutine result.
240+
241+
|Parameter name|Description|Default|
242+
|---|---|---|
243+
|CSS selector|CSS selector used to select an element.|N/A|
244+
|Quantifier expression|A [quantifier expression](#quantifier-expression) is used to control the expected result length.|See [quantifier expression](#quantifier-expression).|
245+
246+
Examples:
247+
248+
```js
249+
// Returns 'bar'.
250+
x('select .foo | remove span | read property textContent', `<div class='foo'>bar<span>baz</span></div>`);
251+
252+
```
253+
232254
#### `read` subroutine
233255

234256
`read` is used to extract value from the matching element using an [evaluator](#evaluators).

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"flow-bin": "^0.57.3",
3333
"flow-copy-source": "^1.3.0",
3434
"husky": "^0.14.3",
35+
"lodash": "^4.17.10",
3536
"nyc": "^11.2.1",
3637
"semantic-release": "^8.2.0",
3738
"sinon": "^4.0.2",

src/evaluators/browserEvaluator.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ export default (): EvaluatorType => {
4444

4545
// eslint-disable-next-line no-unused-vars
4646
const nextUntil = (node, selector, filter) => {
47-
throw new Error('Not implemented.');
47+
throw new Error('Unimplemented.');
48+
};
49+
50+
// eslint-disable-next-line no-unused-vars
51+
const remove = (node) => {
52+
throw new Error('Unimplemented.');
4853
};
4954

5055
return {
@@ -53,6 +58,7 @@ export default (): EvaluatorType => {
5358
isElement,
5459
nextUntil,
5560
parseDocument,
56-
querySelectorAll
61+
querySelectorAll,
62+
remove
5763
};
5864
};

src/evaluators/cheerioEvaluator.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,17 @@ export default (): EvaluatorType => {
7171
});
7272
};
7373

74+
const remove = (node) => {
75+
node.remove();
76+
};
77+
7478
return {
7579
getAttributeValue,
7680
getPropertyValue,
7781
isElement,
7882
nextUntil,
7983
parseDocument,
80-
querySelectorAll
84+
querySelectorAll,
85+
remove
8186
};
8287
};

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {
2121
nextUntilSubroutine,
2222
readSubroutine,
23+
removeSubroutine,
2324
selectSubroutine,
2425
testSubroutine
2526
} from './subroutines';
@@ -46,6 +47,7 @@ export {
4647
const builtInSubroutines = {
4748
nextUntil: nextUntilSubroutine,
4849
read: readSubroutine,
50+
remove: removeSubroutine,
4951
select: selectSubroutine,
5052
test: testSubroutine
5153
};

src/subroutines/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
export {default as nextUntilSubroutine} from './nextUntilSubroutine';
44
export {default as readSubroutine} from './readSubroutine';
5+
export {default as removeSubroutine} from './removeSubroutine';
56
export {default as selectSubroutine} from './selectSubroutine';
67
export {default as testSubroutine} from './testSubroutine';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @flow
2+
3+
import {
4+
FinalResultSentinel
5+
} from 'pianola';
6+
import {
7+
createDebug
8+
} from '../utilities';
9+
import type {
10+
SubroutineType
11+
} from '../types';
12+
import selectSubroutine from './selectSubroutine';
13+
14+
const debug = createDebug('subroutine:remove');
15+
16+
const removeSubroutine: SubroutineType = (subject, [cssSelector, quantifierExpression], {evaluator}) => {
17+
debug('selecting "%s" for removal', cssSelector);
18+
19+
const matches = selectSubroutine(subject, [cssSelector, quantifierExpression], {evaluator});
20+
21+
if (Array.isArray(matches)) {
22+
for (const match of matches) {
23+
evaluator.remove(match);
24+
}
25+
} else if (!(matches instanceof FinalResultSentinel)) {
26+
evaluator.remove(matches);
27+
}
28+
29+
return subject;
30+
};
31+
32+
export default removeSubroutine;

src/types.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export type EvaluatorType = {|
1212
+isElement: (maybeElement: mixed) => boolean,
1313
+nextUntil: (element: ElementType, selector: string, filter?: string) => $ReadOnlyArray<ElementType>,
1414
+parseDocument: (subject: string) => ElementType,
15-
+querySelectorAll: (element: ElementType, selector: string) => $ReadOnlyArray<ElementType>
15+
+querySelectorAll: (element: ElementType, selector: string) => $ReadOnlyArray<ElementType>,
16+
+remove: (element: ElementType) => void
1617
|};
1718

1819
export type SubroutineType = (subject: mixed, parameters: $ReadOnlyArray<string>, bindle: BindleType) => mixed;

test/surgeon/queries/remove.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// @flow
2+
3+
import test from 'ava';
4+
import {
5+
trim
6+
} from 'lodash';
7+
import surgeon, {
8+
SelectSubroutineUnexpectedResultCountError
9+
} from '../../../src';
10+
import type {
11+
DenormalizedQueryType
12+
} from '../../../src/types';
13+
14+
test('removes a single element', (t): void => {
15+
const x = surgeon();
16+
17+
const subject = `
18+
<div class="foo">
19+
<div class="bar"></div>
20+
<div class="baz"></div>
21+
</div>
22+
`;
23+
24+
const query: DenormalizedQueryType = 'select .foo | remove .bar | read property innerHTML';
25+
26+
t.true(trim(x(query, subject)) === '<div class="baz"></div>');
27+
});
28+
29+
test('removes nothing if no nodes are matched {0,1}', (t) => {
30+
const x = surgeon();
31+
32+
const subject = `
33+
<div class="foo">
34+
<div class="baz"></div>
35+
</div>
36+
`;
37+
38+
const query: DenormalizedQueryType = 'select .foo | remove .bar {0,1} | read property innerHTML';
39+
40+
t.true(trim(x(query, subject)) === '<div class="baz"></div>');
41+
});
42+
43+
test('removes multiple elements {0,}', (t) => {
44+
const x = surgeon();
45+
46+
const subject = `
47+
<div class="foo">
48+
<div class="bar"></div>
49+
<div class="baz"></div>
50+
<div class="baz"></div>
51+
<div class="baz"></div>
52+
</div>
53+
`;
54+
55+
const query: DenormalizedQueryType = 'select .foo | remove .baz {0,} | read property innerHTML';
56+
57+
t.true(trim(x(query, subject)) === '<div class="bar"></div>');
58+
});
59+
60+
test('throws error if no nodes are matched', (t): void => {
61+
const x = surgeon();
62+
63+
const subject = `
64+
<div class="foo">
65+
<div class="bar"></div>
66+
<div class="baz"></div>
67+
</div>
68+
`;
69+
70+
const query: DenormalizedQueryType = 'select .foo | remove .qux | read property innerHTML';
71+
72+
t.throws(() => {
73+
x(query, subject);
74+
}, SelectSubroutineUnexpectedResultCountError);
75+
});
76+
77+
test('throws error if more than one node is matched', (t): void => {
78+
const x = surgeon();
79+
80+
const subject = `
81+
<div class="foo">
82+
<div class="bar"></div>
83+
<div class="bar"></div>
84+
</div>
85+
`;
86+
87+
const query: DenormalizedQueryType = 'select .foo | remove .bar | read property innerHTML';
88+
89+
t.throws(() => {
90+
x(query, subject);
91+
}, SelectSubroutineUnexpectedResultCountError);
92+
});

0 commit comments

Comments
 (0)