Skip to content

Commit 6d580bb

Browse files
authored
Merge pull request #92 from sparksuite/validate-url
Implement `validateURL`
2 parents 76341fd + 4adfdc3 commit 6d580bb

19 files changed

+688
-160
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "w3c-css-validator",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "Easily validate CSS using W3C's public CSS validator service",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/build-request-url.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Imports
2+
import buildRequestURL from './build-request-url';
3+
4+
// Tests
5+
describe('#buildRequestURL()', () => {
6+
it('Handles parameters with text value', () => {
7+
expect(
8+
buildRequestURL({
9+
text: '.foo { text-align: center; }',
10+
medium: undefined,
11+
warningLevel: undefined,
12+
})
13+
).toBe(
14+
'https://jigsaw.w3.org/css-validator/validator?text=.foo%20%7B%20text-align%3A%20center%3B%20%7D&usermedium=all&warning=no&output=application/json&profile=css3'
15+
);
16+
});
17+
18+
it('Handles parameters with URL value', () => {
19+
expect(
20+
buildRequestURL({
21+
url: 'https://raw.githubusercontent.com/sparksuite/w3c-css-validator/master/public/css/valid.css',
22+
medium: undefined,
23+
warningLevel: undefined,
24+
})
25+
).toBe(
26+
'https://jigsaw.w3.org/css-validator/validator?uri=https%3A%2F%2Fraw.githubusercontent.com%2Fsparksuite%2Fw3c-css-validator%2Fmaster%2Fpublic%2Fcss%2Fvalid.css&usermedium=all&warning=no&output=application/json&profile=css3'
27+
);
28+
});
29+
30+
it('Uses provided parameters over default values', () => {
31+
expect(
32+
buildRequestURL({
33+
text: '.foo { text-align: center; }',
34+
medium: 'braille',
35+
warningLevel: 3,
36+
})
37+
).toBe(
38+
'https://jigsaw.w3.org/css-validator/validator?text=.foo%20%7B%20text-align%3A%20center%3B%20%7D&usermedium=braille&warning=2&output=application/json&profile=css3'
39+
);
40+
});
41+
42+
it('Complains if text and URL values are provided simultaneously', () => {
43+
expect(() =>
44+
buildRequestURL({
45+
text: '.foo { text-align: center; }',
46+
// @ts-expect-error: We're trying to force an error here
47+
url: 'https://raw.githubusercontent.com/sparksuite/w3c-css-validator/master/public/css/valid.css',
48+
medium: undefined,
49+
warningLevel: undefined,
50+
})
51+
).toThrow('Only a text or a URL value can be provided');
52+
});
53+
});

src/build-request-url.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Imports
2+
import { Parameters } from './types/parameters';
3+
4+
// Helper function that takes in parameters and builds a URL to make a request with
5+
function buildRequestURL(parameters: Parameters): string {
6+
// Validate input
7+
if ('text' in parameters && 'url' in parameters) {
8+
throw new Error('Only a text or a URL value can be provided');
9+
}
10+
11+
// Return request URL
12+
const params = {
13+
...('text' in parameters && parameters.text !== undefined
14+
? {
15+
text: encodeURIComponent(parameters.text),
16+
}
17+
: {
18+
uri: encodeURIComponent(parameters.url),
19+
}),
20+
usermedium: parameters?.medium ?? 'all',
21+
warning: parameters?.warningLevel ? parameters.warningLevel - 1 : 'no',
22+
output: 'application/json',
23+
profile: 'css3',
24+
};
25+
26+
return `https://jigsaw.w3.org/css-validator/validator?${Object.entries(params)
27+
.map(([key, val]) => `${key}=${val}`)
28+
.join('&')}`;
29+
}
30+
31+
export default buildRequestURL;

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Imports
22
import validateText from './validate-text';
3+
import validateURL from './validate-url';
34

45
// Validates CSS using W3C's public CSS validator service
56
const cssValidator = {
67
validateText,
8+
validateURL,
79
};
810

911
export = cssValidator;

src/retrieve-validation/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ export interface W3CCSSValidatorResponse {
99
errors?: {
1010
line: number;
1111
message: string;
12+
source?: string;
1213
}[];
1314
warnings?: {
1415
line: number;
1516
level: 0 | 1 | 2;
1617
message: string;
18+
source?: string;
1719
}[];
1820
};
1921
}

src/types/options.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
interface OptionsBase {
2+
medium?: 'all' | 'braille' | 'embossed' | 'handheld' | 'print' | 'projection' | 'screen' | 'speech' | 'tty' | 'tv';
3+
timeout?: number;
4+
}
5+
6+
export interface OptionsWithoutWarnings extends OptionsBase {
7+
warningLevel?: 0;
8+
}
9+
10+
export interface OptionsWithWarnings extends OptionsBase {
11+
warningLevel: 1 | 2 | 3;
12+
}
13+
14+
export type Options = OptionsWithWarnings | OptionsWithoutWarnings;

src/types/parameters.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Options } from './options';
2+
3+
interface ParametersBase {
4+
medium: Options['medium'];
5+
warningLevel: Options['warningLevel'];
6+
}
7+
8+
interface TextParameters extends ParametersBase {
9+
text: string;
10+
uri?: never;
11+
}
12+
13+
interface URLParameters extends ParametersBase {
14+
url: string;
15+
text?: never;
16+
}
17+
18+
export type Parameters = TextParameters | URLParameters;

src/types/result.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
export interface ValidateTextResultBase {
2+
valid: boolean;
3+
errors: {
4+
line: number;
5+
message: string;
6+
}[];
7+
}
8+
9+
export interface ValidateTextResultWithWarnings extends ValidateTextResultBase {
10+
warnings: {
11+
line: number;
12+
level: 1 | 2 | 3;
13+
message: string;
14+
}[];
15+
}
16+
17+
export interface ValidateTextResultWithoutWarnings extends ValidateTextResultBase {
18+
warnings?: never;
19+
}
20+
21+
export type ValidateTextResult = ValidateTextResultWithWarnings | ValidateTextResultWithoutWarnings;
22+
23+
export interface ValidateURLResultBase {
24+
valid: boolean;
25+
errors: {
26+
line: number;
27+
url: string | null;
28+
message: string;
29+
}[];
30+
}
31+
32+
export interface ValidateURLResultWithWarnings extends ValidateURLResultBase {
33+
warnings: {
34+
line: number;
35+
url: string | null;
36+
level: 1 | 2 | 3;
37+
message: string;
38+
}[];
39+
}
40+
41+
export interface ValidateURLResultWithoutWarnings extends ValidateURLResultBase {
42+
warnings?: never;
43+
}
44+
45+
export type ValidateURLResult = ValidateURLResultWithWarnings | ValidateURLResultWithoutWarnings;

src/validate-options.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Imports
2+
import validateOptions from './validate-options';
3+
4+
// Tests
5+
describe('#validateOptions()', () => {
6+
it('Ignores no options', async () => {
7+
expect(() => validateOptions(undefined)).not.toThrow();
8+
});
9+
10+
it('Ignores valid options', async () => {
11+
expect(() =>
12+
validateOptions({
13+
medium: 'braille',
14+
warningLevel: 1,
15+
timeout: 1000,
16+
})
17+
).not.toThrow();
18+
});
19+
20+
it('Complains about invalid medium', async () => {
21+
// @ts-expect-error: We're trying to force an error here
22+
expect(() => validateOptions({ medium: 'fake' })).toThrow('The medium must be one of the following:');
23+
});
24+
25+
it('Complains about invalid warning level', async () => {
26+
// @ts-expect-error: We're trying to force an error here
27+
expect(() => validateOptions({ warningLevel: 'fake' })).toThrow('The warning level must be one of the following:');
28+
});
29+
30+
it('Complains about negative timeout', async () => {
31+
expect(() => validateOptions({ timeout: -1 })).toThrow('The timeout must be a positive integer');
32+
});
33+
34+
it('Complains about non-integer times', async () => {
35+
expect(() => validateOptions({ timeout: Infinity })).toThrow('The timeout must be an integer');
36+
expect(() => validateOptions({ timeout: 400.1 })).toThrow('The timeout must be an integer');
37+
});
38+
});

src/validate-options.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Imports
2+
import { Options } from './types/options';
3+
4+
// Define supported mediums
5+
const allowedMediums: Options['medium'][] = [
6+
'all',
7+
'braille',
8+
'embossed',
9+
'handheld',
10+
'print',
11+
'projection',
12+
'screen',
13+
'speech',
14+
'tty',
15+
'tv',
16+
];
17+
18+
// Define supported warning levels
19+
const allowedWarningLevels: Options['warningLevel'][] = [0, 1, 2, 3];
20+
21+
// Helper function that validates the supported options
22+
function validateOptions(options?: Options): void {
23+
if (options) {
24+
// Validate medium option
25+
if (options.medium && !allowedMediums.includes(options.medium)) {
26+
throw new Error(`The medium must be one of the following: ${allowedMediums.join(', ')}`);
27+
}
28+
29+
// Validate warning level option
30+
if (options.warningLevel && !allowedWarningLevels.includes(options.warningLevel)) {
31+
throw new Error(`The warning level must be one of the following: ${allowedWarningLevels.join(', ')}`);
32+
}
33+
34+
// Validate timeout option
35+
if (options.timeout !== undefined && !Number.isInteger(options.timeout)) {
36+
throw new Error('The timeout must be an integer');
37+
}
38+
39+
if (options.timeout && options.timeout < 0) {
40+
throw new Error('The timeout must be a positive integer');
41+
}
42+
}
43+
}
44+
45+
export default validateOptions;

src/validate-text.test.ts

-21
Original file line numberDiff line numberDiff line change
@@ -77,27 +77,6 @@ export default function testValidateText(validateText: ValidateText): void {
7777
await expect(validateText(true)).rejects.toThrow('The text to be validated must be a string');
7878
});
7979

80-
it('Complains about invalid medium', async () => {
81-
// @ts-expect-error: We're trying to force an error here
82-
await expect(validateText('abc', { medium: 'fake' })).rejects.toThrow('The medium must be one of the following:');
83-
});
84-
85-
it('Complains about invalid warning level', async () => {
86-
// @ts-expect-error: We're trying to force an error here
87-
await expect(validateText('abc', { warningLevel: 'fake' })).rejects.toThrow(
88-
'The warning level must be one of the following:'
89-
);
90-
});
91-
92-
it('Complains about negative timeout', async () => {
93-
await expect(validateText('abc', { timeout: -1 })).rejects.toThrow('The timeout must be a positive integer');
94-
});
95-
96-
it('Complains about non-integer times', async () => {
97-
await expect(validateText('abc', { timeout: Infinity })).rejects.toThrow('The timeout must be an integer');
98-
await expect(validateText('abc', { timeout: 400.1 })).rejects.toThrow('The timeout must be an integer');
99-
});
100-
10180
it('Throws when the timeout is passed', async () => {
10281
await expect(validateText('abc', { timeout: 1 })).rejects.toThrow('The request took longer than 1ms');
10382
});

0 commit comments

Comments
 (0)