Skip to content

Commit 2eccbc1

Browse files
authored
New option: "classAttribute" (#62)
* Don't track coverage by tests in watch mode * Add the possibility to set plugin options for each test * POC: Use plugin options to define attribute name instead of permanent className * Improve test coverage * Hide Flow error * Make arguments in context key-based * Add test case of computed class value * Add information about new option to README
1 parent 429ca31 commit 2eccbc1

File tree

9 files changed

+266
-48
lines changed

9 files changed

+266
-48
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default const ReactComponent = props => (
3737
* [Eslint integration](#eslint-integration)
3838
* [CSS Modules](#css-modules)
3939
* [Install](#install)
40+
* [Configuration](#configuration)
4041
* [create-react-app](#create-react-app)
4142
* [React Native](#react-native)
4243
* [How it works](#how-it-works)
@@ -157,6 +158,32 @@ It's not supported well yet.
157158
158159
3. Now all your templates written with pug are understood by react and browsers.
159160
161+
### Configuration
162+
163+
| Name | Type | Default | Description
164+
| - | - | - | -
165+
| [`classAttribute`](#classattribute) | `String` | `className` | Attribute name which considered by PUG as "class"
166+
167+
#### `classAttribute`
168+
169+
Default:
170+
171+
```
172+
pug`p.one`
173+
174+
=>
175+
<p className="one" />
176+
```
177+
178+
With "styleName" as value:
179+
180+
```
181+
pug`p.one`
182+
183+
=>
184+
<p styleName="one" />
185+
```
186+
160187
### create-react-app
161188
162189
Integrating with [create-react-app][link to cra] is tricky because it does not allow you to modify babel configuration. There are two documented possibilities:
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`JavaScript output: transformed source code 1`] = `
4+
"const name = 'jack';
5+
6+
module.exports = <div className=\\"first-a first-b\\"><p className=\\"second-a second-b second-c second-d\\" /><p styleName=\\"third-e third-f\\" className=\\"third-a third-b third-c third-d third-g third-h\\" /><p styleName=\\"fourth-a fourth-b\\" /><p className=\\"fivth-a fivth-b\\" /><p styleName=\\"two\\" className={[[\\"one\\", name].join(' ')].join(' ')} />{((_pug_nodes, _pug_arr) => {
7+
if (!(_pug_arr == null || Array.isArray(_pug_arr))) throw new Error('Expected \\"[1, 2, 3]\\" to be an array because it is passed to each.');
8+
if (_pug_arr == null || _pug_arr.length === 0) return undefined;
9+
10+
for (let _pug_index = 0; _pug_index < _pug_arr.length; _pug_index++) {
11+
const item = _pug_arr[_pug_index];
12+
_pug_nodes[_pug_nodes.length] = <p className={['seventh-a', 'seventh-b', \\"seventh-i-\\" + item, \\"seventh-g-\\" + item].join(' ')} key={'pug' + item + ':0'} />;
13+
}
14+
15+
return _pug_nodes;
16+
})([], [1, 2, 3])}</div>;"
17+
`;
18+
19+
exports[`JavaScript output: transformed source code 2`] = `
20+
"const name = 'jack';
21+
22+
module.exports = <div styleName=\\"first-a first-b\\"><p styleName=\\"second-a second-b second-c second-d\\" /><p className=\\"third-g third-h\\" styleName=\\"third-a third-b third-c third-d third-e third-f\\" /><p styleName=\\"fourth-a fourth-b\\" /><p className=\\"fivth-a fivth-b\\" /><p styleName={[[\\"one\\", name].join(' '), \\"two\\"].join(' ')} />{((_pug_nodes, _pug_arr) => {
23+
if (!(_pug_arr == null || Array.isArray(_pug_arr))) throw new Error('Expected \\"[1, 2, 3]\\" to be an array because it is passed to each.');
24+
if (_pug_arr == null || _pug_arr.length === 0) return undefined;
25+
26+
for (let _pug_index = 0; _pug_index < _pug_arr.length; _pug_index++) {
27+
const item = _pug_arr[_pug_index];
28+
_pug_nodes[_pug_nodes.length] = <p className={\\"seventh-g-\\" + item} styleName={['seventh-a', 'seventh-b', \\"seventh-i-\\" + item].join(' ')} key={'pug' + item + ':0'} />;
29+
}
30+
31+
return _pug_nodes;
32+
})([], [1, 2, 3])}</div>;"
33+
`;
34+
35+
exports[`html output: generated html 1`] = `
36+
<div
37+
className="first-a first-b"
38+
>
39+
<p
40+
className="second-a second-b second-c second-d"
41+
/>
42+
<p
43+
className="third-a third-b third-c third-d third-g third-h"
44+
styleName="third-e third-f"
45+
/>
46+
<p
47+
styleName="fourth-a fourth-b"
48+
/>
49+
<p
50+
className="fivth-a fivth-b"
51+
/>
52+
<p
53+
className="one jack"
54+
styleName="two"
55+
/>
56+
<p
57+
className="seventh-a seventh-b seventh-i-1 seventh-g-1"
58+
/>
59+
<p
60+
className="seventh-a seventh-b seventh-i-2 seventh-g-2"
61+
/>
62+
<p
63+
className="seventh-a seventh-b seventh-i-3 seventh-g-3"
64+
/>
65+
</div>
66+
`;
67+
68+
exports[`html output: generated html 2`] = `
69+
<div
70+
styleName="first-a first-b"
71+
>
72+
<p
73+
styleName="second-a second-b second-c second-d"
74+
/>
75+
<p
76+
className="third-g third-h"
77+
styleName="third-a third-b third-c third-d third-e third-f"
78+
/>
79+
<p
80+
styleName="fourth-a fourth-b"
81+
/>
82+
<p
83+
className="fivth-a fivth-b"
84+
/>
85+
<p
86+
styleName="one jack two"
87+
/>
88+
<p
89+
className="seventh-g-1"
90+
styleName="seventh-a seventh-b seventh-i-1"
91+
/>
92+
<p
93+
className="seventh-g-2"
94+
styleName="seventh-a seventh-b seventh-i-2"
95+
/>
96+
<p
97+
className="seventh-g-3"
98+
styleName="seventh-a seventh-b seventh-i-3"
99+
/>
100+
</div>
101+
`;
102+
103+
exports[`static html output: static html 1`] = `"<div class=\\"first-a first-b\\"><p class=\\"second-a second-b second-c second-d\\"></p><p styleName=\\"third-e third-f\\" class=\\"third-a third-b third-c third-d third-g third-h\\"></p><p styleName=\\"fourth-a fourth-b\\"></p><p class=\\"fivth-a fivth-b\\"></p><p styleName=\\"two\\" class=\\"one jack\\"></p><p class=\\"seventh-a seventh-b seventh-i-1 seventh-g-1\\"></p><p class=\\"seventh-a seventh-b seventh-i-2 seventh-g-2\\"></p><p class=\\"seventh-a seventh-b seventh-i-3 seventh-g-3\\"></p></div>"`;
104+
105+
exports[`static html output: static html 2`] = `"<div styleName=\\"first-a first-b\\"><p styleName=\\"second-a second-b second-c second-d\\"></p><p class=\\"third-g third-h\\" styleName=\\"third-a third-b third-c third-d third-e third-f\\"></p><p styleName=\\"fourth-a fourth-b\\"></p><p class=\\"fivth-a fivth-b\\"></p><p styleName=\\"one jack two\\"></p><p class=\\"seventh-g-1\\" styleName=\\"seventh-a seventh-b seventh-i-1\\"></p><p class=\\"seventh-g-2\\" styleName=\\"seventh-a seventh-b seventh-i-2\\"></p><p class=\\"seventh-g-3\\" styleName=\\"seventh-a seventh-b seventh-i-3\\"></p></div>"`;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const name = 'jack';
2+
3+
module.exports = pug`
4+
div.first-a.first-b
5+
p.second-a.second-b(class="second-c second-d")
6+
p.third-a.third-b(
7+
class="third-c third-d"
8+
styleName="third-e third-f"
9+
className="third-g third-h"
10+
)
11+
p(styleName="fourth-a fourth-b")
12+
p(className="fivth-a fivth-b")
13+
p(class=["one", name].join(' ') styleName="two")
14+
15+
each item in [1, 2, 3]
16+
p.seventh-a.seventh-b(
17+
key=item
18+
class="seventh-i-" + item
19+
className="seventh-g-" + item
20+
)
21+
`;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import testHelper, {mockConsoleErrors} from './test-helper';
2+
3+
mockConsoleErrors();
4+
5+
testHelper(__dirname + '/option-class-attribute.input.js');
6+
7+
testHelper(__dirname + '/option-class-attribute.input.js', {
8+
classAttribute: 'styleName',
9+
});

src/__tests__/test-helper.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ import renderer from 'react-test-renderer';
44
import {transformFileSync} from 'babel-core';
55
import transformReactPug from '../';
66

7+
export function mockConsoleErrors() {
8+
const consoleError = console.error.bind(console);
9+
10+
beforeAll(() => {
11+
console.error = jest.fn();
12+
});
13+
14+
afterAll(() => {
15+
console.error = consoleError;
16+
});
17+
}
18+
719
export function testCompileError(filename) {
820
test('Expect an error to be thrown', () => {
921
try {
@@ -39,19 +51,22 @@ export function testRuntimeError(filename) {
3951
});
4052
}
4153

42-
export default filename => {
54+
export default (filename, options = {}) => {
4355
test('JavaScript output', () => {
4456
expect(
4557
transformFileSync(filename, {
4658
babelrc: false,
47-
plugins: [transformReactPug],
59+
plugins: [[transformReactPug, options]],
4860
}).code,
4961
).toMatchSnapshot('transformed source code');
5062
});
5163
test('html output', () => {
5264
const src = transformFileSync(filename, {
5365
babelrc: false,
54-
plugins: [transformReactPug, require('babel-plugin-transform-react-jsx')],
66+
plugins: [
67+
[transformReactPug, options],
68+
require('babel-plugin-transform-react-jsx'),
69+
],
5570
}).code;
5671
const m = {exports: {}};
5772
Function('React,module', src)(React, m);
@@ -62,7 +77,10 @@ export default filename => {
6277
test('static html output', () => {
6378
const src = transformFileSync(filename, {
6479
babelrc: false,
65-
plugins: [transformReactPug, require('babel-plugin-transform-react-jsx')],
80+
plugins: [
81+
[transformReactPug, options],
82+
require('babel-plugin-transform-react-jsx'),
83+
],
6684
}).code;
6785
const m = {exports: {}};
6886
Function('React,module', src)(React, m);

src/context.js

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ type Variable = {
1313
id: Identifier,
1414
};
1515

16+
type Options = {
17+
classAttribute: string,
18+
};
19+
1620
class Context {
1721
key: Key;
1822
file: Object;
@@ -22,23 +26,27 @@ class Context {
2226
_nextBlockID: number = 0;
2327
_parent: ?Context;
2428
_interpolations: ?Map<string, Expression>;
29+
_options: Options;
2530

26-
constructor(
31+
constructor(params: {
2732
definesScope: boolean,
2833
key: Key,
2934
parent: ?Context,
3035
file: Object,
3136
path: Object,
32-
interpolations: ?Map<string, Expression>,
33-
) {
34-
if (!definesScope && parent) {
35-
this.variablesToDeclare = parent.variablesToDeclare;
37+
options: Options,
38+
interpolations?: Map<string, Expression>,
39+
}) {
40+
if (!params.definesScope && params.parent) {
41+
this.variablesToDeclare = params.parent.variablesToDeclare;
3642
}
37-
this._parent = parent;
38-
this.key = key;
39-
this.file = file;
40-
this.path = path;
41-
this._interpolations = interpolations;
43+
44+
this._parent = params.parent;
45+
this.key = params.key;
46+
this.file = params.file;
47+
this.path = params.path;
48+
this._interpolations = params.interpolations;
49+
this._options = params.options;
4250
}
4351

4452
error(code: string, message: string): Error {
@@ -56,41 +64,49 @@ class Context {
5664
}
5765

5866
noKey<T>(fn: (context: Context) => T): T {
59-
const childContext = new Context(
60-
false,
61-
new BaseKey(),
62-
this,
63-
this.file,
64-
this.path,
65-
);
67+
const childContext = new Context({
68+
definesScope: false,
69+
key: new BaseKey(),
70+
parent: this,
71+
file: this.file,
72+
path: this.path,
73+
options: this._options,
74+
});
75+
6676
const result = fn(childContext);
6777
childContext.end();
78+
6879
return result;
6980
}
7081

7182
staticBlock<T>(fn: (context: Context) => T): T {
72-
const childContext = new Context(
73-
false,
74-
new StaticBlock(this.key, this._nextBlockID++),
75-
this,
76-
this.file,
77-
this.path,
78-
);
83+
const childContext = new Context({
84+
definesScope: false,
85+
key: new StaticBlock(this.key, this._nextBlockID++),
86+
parent: this,
87+
file: this.file,
88+
path: this.path,
89+
options: this._options,
90+
});
91+
7992
const result = fn(childContext);
8093
childContext.end();
94+
8195
return result;
8296
}
8397

8498
dynamicBlock<T>(
8599
fn: (context: Context) => T,
86100
): {result: T, variables: Array<Identifier>} {
87-
const childContext = new Context(
88-
true,
89-
new DynamicBlock(this.key, 'src', 0),
90-
this,
91-
this.file,
92-
this.path,
93-
);
101+
const childContext = new Context({
102+
definesScope: true,
103+
key: new DynamicBlock(this.key, 'src', 0),
104+
parent: this,
105+
file: this.file,
106+
path: this.path,
107+
options: this._options,
108+
});
109+
94110
const result = fn(childContext);
95111
childContext.end();
96112
return {result, variables: childContext.variablesToDeclare};
@@ -176,8 +192,17 @@ class Context {
176192
file: Object,
177193
path: Object,
178194
interpolations?: Map<string, Expression>,
195+
params: {options: Options},
179196
) {
180-
return new Context(true, new BaseKey(), null, file, path, interpolations);
197+
return new Context({
198+
definesScope: true,
199+
key: new BaseKey(),
200+
parent: null,
201+
file,
202+
path,
203+
options: params.options,
204+
interpolations,
205+
});
181206
}
182207
}
183208

0 commit comments

Comments
 (0)