Skip to content

Commit

Permalink
Merge pull request #13 from samuelngs/next
Browse files Browse the repository at this point in the history
Add unit tests
  • Loading branch information
samuelngs authored Oct 5, 2024
2 parents 917da21 + c2a9683 commit e610f49
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,4 @@ dist

# Others
.DS_Store
__screenshots__
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"private": false,
"license": "MIT",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
Expand Down
41 changes: 41 additions & 0 deletions src/generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { expect, test } from 'vitest';
import { generateGridStyles, generateLayoutStyles } from './generator';

test('generate layout styles', async () => {
const styles = generateLayoutStyles([
{
name: 'main',
sizes: [
{ name: 'content', size: '800px' },
{ name: 'breakout', size: '1400px' },
],
},
{
name: 'secondary',
sizes: [
{ name: 'content', size: '900px' },
{ name: 'breakout', size: '1200px' },
],
},
]);
expect(styles).toHaveProperty('.grid-cols-main');
expect(styles).toHaveProperty('.grid-rows-main');
expect(styles).toHaveProperty('.grid-cols-secondary');
expect(styles).toHaveProperty('.grid-rows-secondary');
});

test('generate grid styles', () => {
const styles = generateGridStyles([
{
name: 'main',
sizes: [
{ name: 'content', size: '800px' },
{ name: 'breakout', size: '1400px' },
],
},
]);
expect(styles).toHaveProperty('.col-content');
expect(styles).toHaveProperty('.col-breakout');
expect(styles).toHaveProperty('.row-content');
expect(styles).toHaveProperty('.row-breakout');
});
56 changes: 56 additions & 0 deletions src/helpers/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { CSSRuleObject, PluginAPI } from 'tailwindcss/types/config';

interface MockPluginAPI extends PluginAPI {
output(): CSSRuleObject;
}

export function createTailwindMockApi(
theme: Record<string, any>
): MockPluginAPI {
let css = {};

return {
addUtilities(objs: CSSRuleObject | CSSRuleObject[]) {
css = { ...css, ...merge(objs) };
},
matchUtilities() {
throw new Error('Function not implemented.');
},
addComponents() {
throw new Error('Function not implemented.');
},
matchComponents() {
throw new Error('Function not implemented.');
},
addBase() {
throw new Error('Function not implemented.');
},
addVariant() {
throw new Error('Function not implemented.');
},
matchVariant() {
throw new Error('Function not implemented.');
},
theme(path?: string) {
return typeof path === 'string' ? theme[path] : theme;
},
config() {
throw new Error('Function not implemented.');
},
corePlugins() {
throw new Error('Function not implemented.');
},
e(className: string) {
return className;
},
output() {
return css;
},
};
}

function merge(objs: CSSRuleObject | CSSRuleObject[]): CSSRuleObject {
return ([] as CSSRuleObject[])
.concat(objs)
.reduce<CSSRuleObject>((acc, obj) => ({ ...acc, ...obj }), {});
}
11 changes: 7 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import createPlugin from 'tailwindcss/plugin';
import { generateGridStyles, generateLayoutStyles } from './generator';
import { getTailwindLayoutValues } from './layout-values';
import { PluginOptions } from './schema';

export default createPlugin((api) => {
const layouts = getTailwindLayoutValues(api, 'layouts');
export default createPlugin.withOptions((opts?: PluginOptions) => {
return (api) => {
const layouts = getTailwindLayoutValues(api, opts);

api.addUtilities(generateLayoutStyles(layouts));
api.addUtilities(generateGridStyles(layouts));
api.addUtilities(generateLayoutStyles(layouts));
api.addUtilities(generateGridStyles(layouts));
};
});
83 changes: 83 additions & 0 deletions src/layout-values.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { expect, test } from 'vitest';
import { createTailwindMockApi } from './helpers/mock';
import { getTailwindLayoutValues } from './layout-values';
import { Layout } from './schema';

function getValues(theme: Record<string, any>): Layout[] {
const mockApi = createTailwindMockApi(theme);
return getTailwindLayoutValues(mockApi, { throwOnError: true });
}

test('using mutiple layout configurations', async () => {
const values = getValues({
layouts: {
main: {
sizes: {
test: '200px',
},
},
secondary: {
sizes: {
test: '300px',
},
},
},
});
expect(values).toHaveLength(2);
});

test('sorting sizes within the layout configuration', async () => {
const values = getValues({
layouts: {
grid: {
sizes: {
size1: '400px',
size2: '1200px',
size3: '800px',
size4: '600px',
},
},
},
});
expect(values).toHaveLength(1);

const layout = values.at(0)!;
expect(layout.sizes).toHaveLength(4);
expect(layout.sizes[0].name).toBe('size2');
expect(layout.sizes[1].name).toBe('size3');
expect(layout.sizes[2].name).toBe('size4');
expect(layout.sizes[3].name).toBe('size1');
expect(layout.sizes[0].size).toBe('1200px');
expect(layout.sizes[1].size).toBe('800px');
expect(layout.sizes[2].size).toBe('600px');
expect(layout.sizes[3].size).toBe('400px');
});

test('using a size without a length literal', async () => {
expect(
getValues.bind(undefined, {
layouts: {
grid: {
sizes: {
main: 200,
},
},
},
})
).toThrowError('Invalid CSS unit');
});

test('using inconsistent css units', async () => {
expect(
getValues.bind(undefined, {
layouts: {
grid: {
sizes: {
size1: '200px',
size2: '50em',
},
},
},
})
).toThrowError('Inconsistent CSS units');
});
18 changes: 12 additions & 6 deletions src/layout-values.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { PluginAPI } from 'tailwindcss/types/config';
import { n } from './helpers/css-unit';
import { prettyErrors } from './helpers/error';
import { config, Config, Layout } from './schema';
import { getOptions } from './options';
import { config, Config, Layout, PluginOptions } from './schema';

export function getTailwindLayoutValues(
api: PluginAPI,
path: string
params?: PluginOptions
): Layout[] {
try {
const values = config.parse(api.theme(path) ?? {});
const opts = getOptions(params);

try {
const values = config.parse(api.theme(opts.path) ?? {});
return Object.entries(values).reduce<Layout[]>(
(acc, [name, { sizes, padding }]) => {
return acc.concat({
Expand All @@ -21,8 +23,12 @@ export function getTailwindLayoutValues(
[]
);
} catch (err) {
prettyErrors(path, err);
return [];
if (opts.throwOnError) {
throw err;
} else {
prettyErrors(opts.path!, err);
return [];
}
}
}

Expand Down
21 changes: 21 additions & 0 deletions src/options.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from 'vitest';
import { DEFAULT_PATH, DEFAULT_THROW_ON_ERROR, getOptions } from './options';

test('parse undefined options', async () => {
const opts = getOptions();
expect(opts).not.toBeUndefined();
expect(opts).not.toBeNull();
expect(opts).toBeTypeOf('object');
expect(opts.path).toBe(DEFAULT_PATH);
expect(opts.throwOnError).toBe(DEFAULT_THROW_ON_ERROR);
});

test('parse options with custom path', async () => {
const path = 'hello_world';
const opts = getOptions({ path });
expect(opts).not.toBeUndefined();
expect(opts).not.toBeNull();
expect(opts).toBeTypeOf('object');
expect(opts.path).toBe(path);
expect(opts.throwOnError).toBe(DEFAULT_THROW_ON_ERROR);
});
12 changes: 12 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PluginOptions } from './schema';

export const DEFAULT_PATH = 'layouts';
export const DEFAULT_THROW_ON_ERROR = false;

export function getOptions(params?: PluginOptions): PluginOptions {
return {
path: DEFAULT_PATH,
throwOnError: DEFAULT_THROW_ON_ERROR,
...params,
};
}
6 changes: 6 additions & 0 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ export const layout = z.object({
padding: size.optional(),
});

export const pluginOptions = z.object({
path: z.string().optional(),
throwOnError: z.boolean().optional(),
});

export type CSSUnit = z.infer<typeof cssUnit>;
export type Size = z.infer<typeof size>;
export type Config = z.infer<typeof config>;
export type Layout = z.infer<typeof layout>;
export type PluginOptions = z.infer<typeof pluginOptions>;

0 comments on commit e610f49

Please sign in to comment.