Skip to content

Commit 645258d

Browse files
committed
Support compex syntaxes. Add unit and functional tests.
1 parent f2e4e28 commit 645258d

File tree

4 files changed

+174
-20
lines changed

4 files changed

+174
-20
lines changed

.babelrc

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
{
22
"presets": [
33
"es2015",
4-
"stage-0",
5-
"react"
4+
"stage-0"
65
],
76
"plugins": [
87
"add-module-exports",

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"prepublish": "babel --optional runtime src --out-dir lib",
88
"postpublish": "rm -rf lib",
99
"lint": "standard | snazzy",
10-
"test": "echo \"Error: no test specified\" && exit 1"
10+
"test": "./node_modules/.bin/mocha test/*.mocha.js --bail --reporter spec --require babel-polyfill --compilers js:babel-register"
1111
},
1212
"repository": "vmakhaev/react-prefix-loader",
1313
"keywords": [
@@ -26,7 +26,9 @@
2626
"babel-preset-es2015": "^6.3.13",
2727
"babel-preset-react": "^6.3.13",
2828
"babel-preset-stage-0": "^6.3.13",
29+
"babel-register": "^6.24.0",
2930
"babel-runtime": "^6.3.19",
31+
"mocha": "^3.2.0",
3032
"snazzy": "^2.0.1",
3133
"standard": "^5.4.1"
3234
},

src/index.js

+9-17
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,27 @@
1-
const exportDefaultContainerRegex = /export default .*\(([a-zA-Z]*)/
2-
const exportDefaultRegex = /export default ([a-zA-Z]*)/
3-
const classNameRegex = /className=\'([a-zA-Z\-\s]*)\'/g
1+
const exportDefaultContainerRegex = /export\s+default\s+.*\(([A-Z][A-Za-z_\$\d]*)/g
2+
const exportDefaultRegex = /export\s+default(?:\s+class)?\s+([A-Z][A-Za-z_\$\d]*)/g
3+
const classNameRegex = /(className=(?:\{\s*c\(|\{\s*classnames\(|\{\s*)?\s*[\'\"\`]\s*)([a-z][a-zA-Z\d_\-]*)/g
44

55
function loader (source, inputSourceMap) {
66
this.cacheable()
77
let matches = exportDefaultContainerRegex.exec(source) ||
88
exportDefaultRegex.exec(source)
99

1010
if (matches) {
11-
let name = matches[1]
11+
let jsClassName = matches[1]
1212

13-
source = source.replace(classNameRegex, (text, classNames) => {
14-
let prefixedClassNames = classNames
15-
.split(' ')
16-
.map((className) => {
17-
if (ignoreClassName(className, this.options.reactPrefixLoader)) return className
18-
19-
return `${name}-${className}`
20-
})
21-
.join(' ')
22-
23-
return `className='${prefixedClassNames}'`
13+
source = source.replace(classNameRegex, (match, prefix, cssClassName) => {
14+
if (ignoreClassName(cssClassName, this.options.reactPrefixLoader)) return prefix + cssClassName
15+
if (cssClassName === 'root') return `${prefix}${jsClassName}`
16+
return `${prefix}${jsClassName}-${cssClassName}`
2417
})
2518
}
2619

2720
this.callback(null, source, inputSourceMap)
2821
}
2922

3023
function ignoreClassName (className, options = {}) {
31-
return classMatchesTest(className, options.ignore) ||
32-
className.trim().length === 0 || /^[A-Z-]/.test(className)
24+
return classMatchesTest(className, options.ignore) || /^[A-Z-]/.test(className)
3325
}
3426

3527
function classMatchesTest (className, ignore) {

test/index.mocha.js

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import assert from 'assert'
2+
import loader from '../src/index'
3+
4+
const classNameRegex = /(className=(?:\{\s*c\(|\{\s*classnames\(|\{\s*)?\s*[\'\"\`]\s*)([a-z][a-zA-Z\d_\-]*)/
5+
let classNameTests = [
6+
{in: 'className=\'root\'', out: 'root'},
7+
{in: 'className=\'Root\'', out: false},
8+
{in: 'className="root"', out: 'root'},
9+
{in: 'className="Root"', out: false},
10+
{in: 'className={"root " + this.props.className}', out: 'root'},
11+
{in: '<div className={"body " + this.props.className}></div>', out: 'body'},
12+
{in: 'className={\'root \' + this.props.className}', out: 'root'},
13+
{in: 'className={`body${this.props.hello}` + this.props.className}', out: 'body'},
14+
{in: 'className={c(\'root Hello\', {} ) }', out: 'root'},
15+
{in: 'className={ c( \'root Hello\', {} ) }', out: 'root'},
16+
{in: 'className={classnames(\' root Hello\', {} ) }', out: 'root'},
17+
{in: 'className={ classnames( \' root Hello\', {} ) }', out: 'root'},
18+
{in: 'className={c(\'Hello root\')}', out: false},
19+
{in: 'className={c(\`root${\' hello\'}\`)}', out: 'root'},
20+
{in: 'className={c(\`root ${classNames}\`, {\'-selected\': true})}', out: 'root'},
21+
{in: 'className={c(\"root-hello\", {\'-selected\': true})}', out: 'root-hello'},
22+
{in: 'className={c(\"Hello\", {\'-selected\': true})}', out: false}
23+
]
24+
25+
describe('className regexps', () => {
26+
27+
classNameTests.forEach(testItem => {
28+
it(testItem.in, () => {
29+
let match = testItem.in.match(classNameRegex)
30+
if (testItem.out) {
31+
assert.equal(!!(match && match[1] && match[2]), true)
32+
assert.deepEqual(match[2], testItem.out)
33+
} else {
34+
assert.equal(match, null)
35+
}
36+
})
37+
})
38+
39+
})
40+
41+
const exportDefaultRegex = /export\s+default(?:\s+class)?\s+([A-Z][A-Za-z_\$\d]*)/
42+
let exportDefaultTests = [
43+
{in: 'export default MyClass', out: 'MyClass'},
44+
{in: 'export default MyClass', out: 'MyClass'},
45+
{in: 'export default class MyClass', out: 'MyClass'},
46+
{in: 'export default class MyClass', out: 'MyClass'},
47+
{in: 'export default class MyClass222 extends React.Component {', out: 'MyClass222'},
48+
{in: 'export default class MyClass222 extends React.Component {', out: 'MyClass222'},
49+
{in: 'export default myClass', out: false},
50+
{in: 'export default class myClass', out: false},
51+
{in: 'export default 2MyClass', out: false},
52+
{in: 'export default class 2MyClass', out: false},
53+
]
54+
55+
describe('export default regexps', () => {
56+
57+
exportDefaultTests.forEach(testItem => {
58+
it(testItem.in, () => {
59+
let match = testItem.in.match(exportDefaultRegex)
60+
if (testItem.out) {
61+
assert.equal(!!(match && match[1]), true)
62+
assert.deepEqual(match[1], testItem.out)
63+
} else {
64+
assert.equal(match, null)
65+
}
66+
})
67+
})
68+
69+
})
70+
71+
const exportDefaultContainerRegex = /export\s+default\s+.*\(([A-Z][A-Za-z_\$\d]*)/
72+
let exportDefaultContainerTests = [
73+
{in: 'export default MyClass', out: false},
74+
{in: 'export default connect(MyClass)', out: 'MyClass'},
75+
{in: 'export default subscribe(connect(MyClass))', out: 'MyClass'},
76+
{in: 'export default connect(myClass)', out: false},
77+
{in: 'export default subscribe(connect(myClass))', out: false},
78+
{in: 'export default connect(2MyClass)', out: false},
79+
{in: 'export default subscribe(connect(2MyClass))', out: false},
80+
]
81+
82+
describe('export default container regexps', () => {
83+
84+
exportDefaultContainerTests.forEach(testItem => {
85+
it(testItem.in, () => {
86+
let match = testItem.in.match(exportDefaultContainerRegex)
87+
if (testItem.out) {
88+
assert.equal(!!(match && match[1]), true)
89+
assert.deepEqual(match[1], testItem.out)
90+
} else {
91+
assert.equal(match, null)
92+
}
93+
})
94+
})
95+
96+
})
97+
98+
let loaderFunctionalTest = {
99+
in: `
100+
export default class MyClass extends React.Component {
101+
render () {
102+
return (
103+
<div className={c(\`root \${this.props.className} -hello\`, {
104+
'-selected': true
105+
})}
106+
<div className='title'></div>
107+
<div className='title'></div>
108+
<div className='title'></div>
109+
<div className='-something'></div>
110+
<div className='title'></div>
111+
<div className='Something'></div>
112+
<div className={classnames('left', {})}></div>
113+
<div className={classnames('Topbar-left', {})}></div>
114+
<div className={"body " + this.props.className}></div>
115+
<div className={c("body " + this.props.className)}></div>
116+
<div className='title'></div>
117+
<div className={"body " + this.props.className}></div>
118+
</div>
119+
)
120+
}
121+
}
122+
`, out: `
123+
export default class MyClass extends React.Component {
124+
render () {
125+
return (
126+
<div className={c(\`MyClass \${this.props.className} -hello\`, {
127+
'-selected': true
128+
})}
129+
<div className='MyClass-title'></div>
130+
<div className='MyClass-title'></div>
131+
<div className='MyClass-title'></div>
132+
<div className='-something'></div>
133+
<div className='MyClass-title'></div>
134+
<div className='Something'></div>
135+
<div className={classnames('MyClass-left', {})}></div>
136+
<div className={classnames('Topbar-left', {})}></div>
137+
<div className={"MyClass-body " + this.props.className}></div>
138+
<div className={c("MyClass-body " + this.props.className)}></div>
139+
<div className='MyClass-title'></div>
140+
<div className={"MyClass-body " + this.props.className}></div>
141+
</div>
142+
)
143+
}
144+
}
145+
`
146+
}
147+
describe.only('loader', () => {
148+
149+
it('big functional test of loader', () => {
150+
let context = {
151+
options: {},
152+
cacheable: () => {},
153+
callback: (err, transformedSource) => {
154+
assert.equal(err, null)
155+
assert.deepEqual(transformedSource, loaderFunctionalTest.out)
156+
}
157+
}
158+
loader.call(context, loaderFunctionalTest.in)
159+
})
160+
161+
})

0 commit comments

Comments
 (0)