Skip to content

Commit 27c4db5

Browse files
authored
Merge pull request #116 from css-modules/contracts
Test contracts
2 parents f7848d0 + 16eb56c commit 27c4db5

File tree

4 files changed

+280
-14
lines changed

4 files changed

+280
-14
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[travis-img]: https://travis-ci.org/css-modules/postcss-icss-selectors.svg
55
[travis]: https://travis-ci.org/css-modules/postcss-icss-selectors
66

7-
PostCSS plugin for css modules to to local-scope classes and ids
7+
PostCSS plugin for css modules to local-scope classes and ids
88

99
## Usage
1010

@@ -70,6 +70,18 @@ In global mode
7070
Converts every new local name in #id or .class defintion to global alias.
7171
By default returns `[name]__[local]---[hash:base64:5]`.
7272

73+
### Messages
74+
75+
postcss-icss-selectors passes result.messages for each local-scoped class or id
76+
77+
```
78+
{
79+
plugin: 'postcss-icss-selectors',
80+
type: 'icss-scoped',
81+
name: string, // local-scoped identifier
82+
value: string // generated global identifier
83+
}
84+
```
7385

7486
## License
7587

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "postcss-icss-selectors",
33
"version": "1.2.0",
4-
"description": "PostCSS plugin for css modules to to local-scope classes and ids",
4+
"description": "PostCSS plugin for css modules to local-scope classes and ids",
55
"keywords": [
66
"css-modules",
77
"postcss",

src/index.js

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,60 @@ const walkRules = (css, callback) => {
111111
});
112112
};
113113

114-
module.exports = postcss.plugin(plugin, (options = {}) => css => {
114+
const addExports = (css, aliases) => {
115+
const { icssImports, icssExports } = extractICSS(css);
116+
const exports = Object.assign({}, icssExports, aliases);
117+
css.prepend(createICSSRules(icssImports, exports));
118+
};
119+
120+
const getMessages = aliases =>
121+
Object.keys(aliases)
122+
.map(name => ({ plugin, type: "icss-scoped", name, value: aliases[name] }))
123+
.reduce((acc, msg) => [...acc, msg], []);
124+
125+
const isValue = (messages, name) =>
126+
messages.find(msg => msg.type === "icss-value" && msg.value === name);
127+
128+
const isRedeclared = (messages, name) =>
129+
messages.find(msg => msg.type === "icss-scoped" && msg.name === name);
130+
131+
const getComposed = (messages, name) =>
132+
messages
133+
.filter(msg => msg.type === "icss-composed" && msg.name === name)
134+
.map(msg => msg.value);
135+
136+
const composeAliases = (aliases, messages) =>
137+
Object.keys(aliases).reduce(
138+
(acc, name) =>
139+
Object.assign({}, acc, {
140+
[name]: [aliases[name], ...getComposed(messages, name)].join(" ")
141+
}),
142+
{}
143+
);
144+
145+
module.exports = postcss.plugin(plugin, (options = {}) => (css, result) => {
115146
const generateScopedName =
116147
options.generateScopedName ||
117148
genericNames("[name]__[local]---[hash:base64:5]");
118149
const input = (css && css.source && css.source.input) || {};
119-
const { icssImports, icssExports } = extractICSS(css);
120150
const aliases = {};
121-
const getAlias = name => {
122-
const alias = generateScopedName(name, input.from, input.css);
123-
aliases[name] = alias;
124-
return alias;
125-
};
126151
walkRules(css, rule => {
152+
const getAlias = name => {
153+
if (aliases[name]) {
154+
return aliases[name];
155+
}
156+
// icss-value contract
157+
if (isValue(result.messages, name)) {
158+
return name;
159+
}
160+
const alias = generateScopedName(name, input.from, input.css);
161+
// icss-scoped contract
162+
if (isRedeclared(result.messages, name)) {
163+
result.warn(`'${name}' already declared`, { node: rule });
164+
}
165+
aliases[name] = alias;
166+
return alias;
167+
};
127168
try {
128169
rule.selector = localizeSelectors(
129170
rule.selector,
@@ -134,7 +175,7 @@ module.exports = postcss.plugin(plugin, (options = {}) => css => {
134175
throw rule.error(e.message);
135176
}
136177
});
137-
css.prepend(
138-
createICSSRules(icssImports, Object.assign({}, icssExports, aliases))
139-
);
178+
result.messages.push(...getMessages(aliases));
179+
// icss-composed contract
180+
addExports(css, composeAliases(aliases, result.messages));
140181
});

test/test.js

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import postcss from "postcss";
33
import stripIndent from "strip-indent";
44
import plugin from "../src";
55

6-
const strip = input =>
7-
stripIndent(input).replace(/^\n/, "").replace(/\s+$/, "");
6+
const strip = input => stripIndent(input).trim();
87
const compile = (input, options) =>
98
postcss([plugin(options)])
109
.process(input, options)
1110
.catch(e => Promise.reject(e.message));
1211
const generateScopedName = name => `__scope__${name}`;
12+
const messagesPlugin = messages => (css, result) => {
13+
result.messages.push(...messages);
14+
};
1315

1416
const runCSS = ({ fixture, expected, options }) => {
1517
return expect(
@@ -26,6 +28,28 @@ const runError = ({ fixture, error, options }) => {
2628
);
2729
};
2830

31+
const runMessages = ({
32+
fixture,
33+
inputMessages = [],
34+
outputMessages,
35+
warnings = [],
36+
expected
37+
}) => {
38+
const processor = postcss([
39+
messagesPlugin(inputMessages),
40+
plugin({ generateScopedName })
41+
]).process(strip(fixture));
42+
return processor.then(result => {
43+
expect(result.messages.filter(msg => msg.type !== "warning")).toEqual(
44+
outputMessages
45+
);
46+
expect(result.warnings().map(msg => msg.text)).toEqual(warnings);
47+
if (expected) {
48+
expect(result.css).toEqual(strip(expected));
49+
}
50+
});
51+
};
52+
2953
test("scope selectors", () => {
3054
return runCSS({
3155
fixture: `
@@ -657,3 +681,192 @@ test("save :import statemtents", () => {
657681
`
658682
});
659683
});
684+
685+
test("dispatch messages with all locals", () => {
686+
return runMessages({
687+
fixture: `
688+
.foo {}
689+
.foo .bar {}
690+
:global .baz :local(.zab) {}
691+
`,
692+
outputMessages: [
693+
{
694+
plugin: "postcss-icss-selectors",
695+
type: "icss-scoped",
696+
name: "foo",
697+
value: "__scope__foo"
698+
},
699+
{
700+
plugin: "postcss-icss-selectors",
701+
type: "icss-scoped",
702+
name: "bar",
703+
value: "__scope__bar"
704+
},
705+
{
706+
plugin: "postcss-icss-selectors",
707+
type: "icss-scoped",
708+
name: "zab",
709+
value: "__scope__zab"
710+
}
711+
]
712+
});
713+
});
714+
715+
test("icss-scoped contract", () => {
716+
const inputMessages = [
717+
{
718+
plugin: "previous-plugin",
719+
type: "icss-scoped",
720+
name: "foo",
721+
value: "__declared__foo"
722+
}
723+
];
724+
return runMessages({
725+
fixture: `
726+
:export {
727+
foo: __declared__foo
728+
}
729+
.foo {}
730+
.bar {}
731+
.foo {}
732+
`,
733+
expected: `
734+
:export {
735+
foo: __scope__foo;
736+
bar: __scope__bar
737+
}
738+
.__scope__foo {}
739+
.__scope__bar {}
740+
.__scope__foo {}
741+
`,
742+
inputMessages,
743+
outputMessages: [
744+
...inputMessages,
745+
{
746+
plugin: "postcss-icss-selectors",
747+
type: "icss-scoped",
748+
name: "foo",
749+
value: "__scope__foo"
750+
},
751+
{
752+
plugin: "postcss-icss-selectors",
753+
type: "icss-scoped",
754+
name: "bar",
755+
value: "__scope__bar"
756+
}
757+
],
758+
warnings: [`'foo' already declared`]
759+
});
760+
});
761+
762+
test("icss-composed contract", () => {
763+
const inputMessages = [
764+
{
765+
plugin: "previous-plugin",
766+
type: "icss-composed",
767+
name: "foo",
768+
value: "__compose__foo1"
769+
},
770+
{
771+
plugin: "previous-plugin",
772+
type: "icss-composed",
773+
name: "foo",
774+
value: "__compose__foo2"
775+
},
776+
{
777+
plugin: "previous-plugin",
778+
type: "icss-composed",
779+
name: "bar",
780+
value: "__compose__bar"
781+
}
782+
];
783+
return runMessages({
784+
fixture: `
785+
:export {
786+
foo: __compose__foo;
787+
baz: __declared__baz
788+
}
789+
.foo {}
790+
.bar {}
791+
.baz {}
792+
`,
793+
expected: `
794+
:export {
795+
foo: __scope__foo __compose__foo1 __compose__foo2;
796+
baz: __scope__baz;
797+
bar: __scope__bar __compose__bar
798+
}
799+
.__scope__foo {}
800+
.__scope__bar {}
801+
.__scope__baz {}
802+
`,
803+
inputMessages,
804+
outputMessages: [
805+
...inputMessages,
806+
{
807+
plugin: "postcss-icss-selectors",
808+
type: "icss-scoped",
809+
name: "foo",
810+
value: "__scope__foo"
811+
},
812+
{
813+
plugin: "postcss-icss-selectors",
814+
type: "icss-scoped",
815+
name: "bar",
816+
value: "__scope__bar"
817+
},
818+
{
819+
plugin: "postcss-icss-selectors",
820+
type: "icss-scoped",
821+
name: "baz",
822+
value: "__scope__baz"
823+
}
824+
]
825+
});
826+
});
827+
828+
test("icss-value contract", () => {
829+
const inputMessages = [
830+
{
831+
plugin: "previous-plugin",
832+
type: "icss-value",
833+
name: "foo",
834+
value: "__declared__foo"
835+
},
836+
{
837+
plugin: "previous-plugin",
838+
type: "icss-value",
839+
name: "bar",
840+
value: "__declared__bar"
841+
}
842+
];
843+
return runMessages({
844+
fixture: `
845+
:export {
846+
foo: __declared__foo
847+
}
848+
.__declared__foo {}
849+
.__declared__bar {}
850+
.baz {}
851+
`,
852+
expected: `
853+
:export {
854+
foo: __declared__foo;
855+
baz: __scope__baz
856+
}
857+
.__declared__foo {}
858+
.__declared__bar {}
859+
.__scope__baz {}
860+
`,
861+
inputMessages,
862+
outputMessages: [
863+
...inputMessages,
864+
{
865+
plugin: "postcss-icss-selectors",
866+
type: "icss-scoped",
867+
name: "baz",
868+
value: "__scope__baz"
869+
}
870+
]
871+
});
872+
});

0 commit comments

Comments
 (0)