@versatiles/style 5.2.6 → 5.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +274 -14
- package/dist/index.js +3650 -11
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/color/abstract.ts +83 -0
- package/src/color/hsl.test.ts +182 -0
- package/src/color/hsl.ts +122 -0
- package/src/color/hsv.test.ts +174 -0
- package/src/color/hsv.ts +100 -0
- package/src/color/index.test.ts +119 -0
- package/src/color/index.ts +38 -0
- package/src/color/random.test.ts +35 -0
- package/src/color/random.ts +165 -0
- package/src/color/rgb.test.ts +227 -0
- package/src/color/rgb.ts +248 -0
- package/src/color/utils.test.ts +86 -0
- package/src/color/utils.ts +13 -0
- package/src/guess_style/guess_style.test.ts +134 -0
- package/src/guess_style/guess_style.ts +166 -0
- package/{dist/guess_style/index.d.ts → src/guess_style/index.ts} +1 -0
- package/src/index.test.ts +77 -0
- package/src/index.ts +18 -0
- package/src/lib/utils.test.ts +197 -0
- package/src/lib/utils.ts +134 -0
- package/{dist/shortbread/index.d.ts → src/shortbread/index.ts} +1 -0
- package/src/shortbread/layers.test.ts +36 -0
- package/src/shortbread/layers.ts +564 -0
- package/src/shortbread/properties.test.ts +44 -0
- package/src/shortbread/properties.ts +142 -0
- package/src/shortbread/template.test.ts +43 -0
- package/src/shortbread/template.ts +343 -0
- package/src/style_builder/decorator.test.ts +67 -0
- package/src/style_builder/decorator.ts +135 -0
- package/src/style_builder/recolor.test.ts +306 -0
- package/src/style_builder/recolor.ts +110 -0
- package/src/style_builder/style_builder.test.ts +103 -0
- package/src/style_builder/style_builder.ts +134 -0
- package/src/style_builder/types.ts +141 -0
- package/src/styles/LICENSE.md +41 -0
- package/src/styles/colorful.ts +1041 -0
- package/src/styles/eclipse.ts +11 -0
- package/{dist/styles/empty.d.ts → src/styles/empty.ts} +7 -3
- package/src/styles/graybeard.ts +11 -0
- package/src/styles/index.ts +33 -0
- package/src/styles/neutrino.ts +429 -0
- package/{dist/types/index.d.ts → src/types/index.ts} +1 -0
- package/{dist/types/maplibre.d.ts → src/types/maplibre.ts} +3 -0
- package/src/types/tilejson.test.ts +94 -0
- package/src/types/tilejson.ts +125 -0
- package/src/types/vector_layer.test.ts +64 -0
- package/src/types/vector_layer.ts +69 -0
- package/dist/color/abstract.d.ts +0 -34
- package/dist/color/abstract.js +0 -53
- package/dist/color/abstract.js.map +0 -1
- package/dist/color/hsl.d.ts +0 -23
- package/dist/color/hsl.js +0 -98
- package/dist/color/hsl.js.map +0 -1
- package/dist/color/hsv.d.ts +0 -20
- package/dist/color/hsv.js +0 -100
- package/dist/color/hsv.js.map +0 -1
- package/dist/color/index.d.ts +0 -6
- package/dist/color/index.js +0 -29
- package/dist/color/index.js.map +0 -1
- package/dist/color/random.d.ts +0 -9
- package/dist/color/random.js +0 -134
- package/dist/color/random.js.map +0 -1
- package/dist/color/rgb.d.ts +0 -28
- package/dist/color/rgb.js +0 -195
- package/dist/color/rgb.js.map +0 -1
- package/dist/color/utils.d.ts +0 -3
- package/dist/color/utils.js +0 -10
- package/dist/color/utils.js.map +0 -1
- package/dist/guess_style/guess_style.d.ts +0 -8
- package/dist/guess_style/guess_style.js +0 -147
- package/dist/guess_style/guess_style.js.map +0 -1
- package/dist/guess_style/index.js +0 -2
- package/dist/guess_style/index.js.map +0 -1
- package/dist/lib/utils.d.ts +0 -6
- package/dist/lib/utils.js +0 -126
- package/dist/lib/utils.js.map +0 -1
- package/dist/shortbread/index.js +0 -3
- package/dist/shortbread/index.js.map +0 -1
- package/dist/shortbread/layers.d.ts +0 -5
- package/dist/shortbread/layers.js +0 -521
- package/dist/shortbread/layers.js.map +0 -1
- package/dist/shortbread/properties.d.ts +0 -7
- package/dist/shortbread/properties.js +0 -125
- package/dist/shortbread/properties.js.map +0 -1
- package/dist/shortbread/template.d.ts +0 -4
- package/dist/shortbread/template.js +0 -339
- package/dist/shortbread/template.js.map +0 -1
- package/dist/style_builder/decorator.d.ts +0 -4
- package/dist/style_builder/decorator.js +0 -127
- package/dist/style_builder/decorator.js.map +0 -1
- package/dist/style_builder/recolor.d.ts +0 -22
- package/dist/style_builder/recolor.js +0 -89
- package/dist/style_builder/recolor.js.map +0 -1
- package/dist/style_builder/style_builder.d.ts +0 -15
- package/dist/style_builder/style_builder.js +0 -106
- package/dist/style_builder/style_builder.js.map +0 -1
- package/dist/style_builder/types.d.ts +0 -122
- package/dist/style_builder/types.js +0 -3
- package/dist/style_builder/types.js.map +0 -1
- package/dist/styles/colorful.d.ts +0 -11
- package/dist/styles/colorful.js +0 -956
- package/dist/styles/colorful.js.map +0 -1
- package/dist/styles/eclipse.d.ts +0 -5
- package/dist/styles/eclipse.js +0 -9
- package/dist/styles/eclipse.js.map +0 -1
- package/dist/styles/empty.js +0 -8
- package/dist/styles/empty.js.map +0 -1
- package/dist/styles/graybeard.d.ts +0 -5
- package/dist/styles/graybeard.js +0 -9
- package/dist/styles/graybeard.js.map +0 -1
- package/dist/styles/index.d.ts +0 -11
- package/dist/styles/index.js +0 -20
- package/dist/styles/index.js.map +0 -1
- package/dist/styles/neutrino.d.ts +0 -11
- package/dist/styles/neutrino.js +0 -401
- package/dist/styles/neutrino.js.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/types/maplibre.js +0 -2
- package/dist/types/maplibre.js.map +0 -1
- package/dist/types/tilejson.d.ts +0 -32
- package/dist/types/tilejson.js +0 -87
- package/dist/types/tilejson.js.map +0 -1
- package/dist/types/vector_layer.d.ts +0 -14
- package/dist/types/vector_layer.js +0 -51
- package/dist/types/vector_layer.js.map +0 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Color } from '../color/index.js';
|
|
2
|
+
import { deepClone, isSimpleObject, isBasicType, deepMerge, resolveUrl, basename } from './utils.js';
|
|
3
|
+
|
|
4
|
+
describe('deepClone', () => {
|
|
5
|
+
it('clones primitive types correctly', () => {
|
|
6
|
+
expect(deepClone(10)).toBe(10);
|
|
7
|
+
expect(deepClone(true)).toBe(true);
|
|
8
|
+
expect(deepClone('string')).toBe('string');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('clones an array correctly', () => {
|
|
12
|
+
const array = [1, 2, { a: 'b' }];
|
|
13
|
+
const clonedArray = deepClone(array);
|
|
14
|
+
expect(clonedArray).toEqual(array);
|
|
15
|
+
expect(clonedArray).not.toBe(array);
|
|
16
|
+
expect(clonedArray[2]).not.toBe(array[2]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('clones a simple object correctly', () => {
|
|
20
|
+
const obj = { a: 'b', c: 1, d: true };
|
|
21
|
+
const clonedObj = deepClone(obj);
|
|
22
|
+
expect(clonedObj).toEqual(obj);
|
|
23
|
+
expect(clonedObj).not.toBe(obj);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('clones a Color instance correctly', () => {
|
|
27
|
+
const color = Color.parse('#FF5733');
|
|
28
|
+
const clonedColor = deepClone(color);
|
|
29
|
+
expect(clonedColor.asHex()).toBe(color.asHex());
|
|
30
|
+
expect(clonedColor).not.toBe(color);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('throws an error for non-implemented types', () => {
|
|
34
|
+
const func = (): boolean => true;
|
|
35
|
+
expect(() => deepClone(func)).toThrow('Not implemented yet: "function" case');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('throws an error for unexpected cases', () => {
|
|
39
|
+
const obj = Object.create(null); // No prototype
|
|
40
|
+
expect(() => deepClone(obj)).toThrow();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('isSimpleObject', () => {
|
|
45
|
+
it('identifies simple objects correctly', () => {
|
|
46
|
+
expect(isSimpleObject({ a: 1 })).toBe(true);
|
|
47
|
+
expect(isSimpleObject({})).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects non-objects', () => {
|
|
51
|
+
expect(isSimpleObject(1)).toBe(false);
|
|
52
|
+
expect(isSimpleObject('a')).toBe(false);
|
|
53
|
+
expect(isSimpleObject(true)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('rejects arrays', () => {
|
|
57
|
+
expect(isSimpleObject([1, 2, 3])).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('rejects objects with prototype properties', () => {
|
|
61
|
+
class MyClass {
|
|
62
|
+
readonly property = true;
|
|
63
|
+
}
|
|
64
|
+
expect(isSimpleObject(new MyClass())).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('rejects null objects', () => {
|
|
68
|
+
expect(isSimpleObject(null)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('isBasicType', () => {
|
|
73
|
+
it('returns true for basic types', () => {
|
|
74
|
+
expect(isBasicType(1)).toBe(true);
|
|
75
|
+
expect(isBasicType('string')).toBe(true);
|
|
76
|
+
expect(isBasicType(true)).toBe(true);
|
|
77
|
+
expect(isBasicType(undefined)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('returns false for objects', () => {
|
|
81
|
+
expect(isBasicType({})).toBe(false);
|
|
82
|
+
expect(isBasicType([])).toBe(false);
|
|
83
|
+
expect(isBasicType(Color.parse('#FF5733'))).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('throws an error for unsupported types like functions', () => {
|
|
87
|
+
expect(() => isBasicType(() => true)).toThrow('unknown type: function');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('deepMerge', () => {
|
|
92
|
+
it('merges simple objects correctly', () => {
|
|
93
|
+
const target = { a: 1, b: 2, c: 3 };
|
|
94
|
+
const source = { b: 3, c: 4 };
|
|
95
|
+
const result = deepMerge(target, source);
|
|
96
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('merges nested objects correctly', () => {
|
|
100
|
+
const target = { a: { d: 4, e: 3 }, b: 2 } as { a: { d?: number, e: number }, b: number };
|
|
101
|
+
const source = { a: { e: 5 }, b: 3 };
|
|
102
|
+
const result = deepMerge(target, source);
|
|
103
|
+
expect(result).toEqual({ a: { d: 4, e: 5 }, b: 3 });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('overrides primitives with objects', () => {
|
|
107
|
+
const target = { a: 1, b: 2 } as { a: object | number, b: object | number };
|
|
108
|
+
const source = { a: { d: 4 }, b: { e: 5 } };
|
|
109
|
+
const result = deepMerge(target, source);
|
|
110
|
+
expect(result).toEqual({ a: { d: 4 }, b: { e: 5 } });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('merges Color instances correctly', () => {
|
|
114
|
+
const target = { color: Color.parse('#FF5733') };
|
|
115
|
+
const source = { color: Color.parse('#33FF57') };
|
|
116
|
+
const result = deepMerge(target, source);
|
|
117
|
+
expect(result.color.asHex()).toBe('#33FF57');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('throws error for unsupported cases', () => {
|
|
121
|
+
const target = { a: (): boolean => false } as { a: (() => boolean) | object };
|
|
122
|
+
const source = { a: { b: 1 } };
|
|
123
|
+
expect(() => deepMerge(target, source)).toThrow('Not implemented yet: "function" case');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('resolveUrl', () => {
|
|
128
|
+
it('resolves a relative URL with a base URL', () => {
|
|
129
|
+
expect(resolveUrl('http://example.com/', 'path/page')).toBe('http://example.com/path/page');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('returns the same URL if the base URL is empty', () => {
|
|
133
|
+
expect(resolveUrl('', 'http://example.com/path/page')).toBe('http://example.com/path/page');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('returns the correct URL if an absolute URL is used', () => {
|
|
137
|
+
expect(resolveUrl('http://example1.com', 'http://example.com/path/page')).toBe('http://example.com/path/page');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('handles URLs with special characters', () => {
|
|
141
|
+
expect(resolveUrl('http://example.com/', 'path/{param}')).toBe('http://example.com/path/{param}');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('handles URLs already containing encoded special characters', () => {
|
|
145
|
+
expect(resolveUrl('http://example.com/', 'path/%7Bparam%7D')).toBe('http://example.com/path/{param}');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('throws an error for invalid base URLs', () => {
|
|
149
|
+
expect(() => resolveUrl('invalid-base', 'path/page')).toThrow('Invalid URL');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('basename', () => {
|
|
154
|
+
it('returns the last segment of a URL-like string', () => {
|
|
155
|
+
expect(basename('http://example.com/path/to/resource')).toBe('resource');
|
|
156
|
+
expect(basename('/path/to/file.txt')).toBe('file.txt');
|
|
157
|
+
expect(basename('folder/subfolder/item')).toBe('item');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('removes trailing slashes before extracting basename', () => {
|
|
161
|
+
expect(basename('http://example.com/path/to/resource/')).toBe('resource');
|
|
162
|
+
expect(basename('/path/to/folder/')).toBe('folder');
|
|
163
|
+
expect(basename('directory/')).toBe('directory');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('returns an empty string for root paths or URLs ending with a slash', () => {
|
|
167
|
+
expect(basename('http://example.com/')).toBe('');
|
|
168
|
+
expect(basename('/')).toBe('');
|
|
169
|
+
expect(basename('////')).toBe('');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('returns the full string if no slashes are present', () => {
|
|
173
|
+
expect(basename('file')).toBe('file');
|
|
174
|
+
expect(basename('filename.txt')).toBe('filename.txt');
|
|
175
|
+
expect(basename('no/slash')).toBe('slash');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('handles empty and null inputs gracefully', () => {
|
|
179
|
+
expect(basename('')).toBe('');
|
|
180
|
+
expect(basename(null as unknown as string)).toBe('');
|
|
181
|
+
expect(basename(undefined as unknown as string)).toBe('');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('handles URLs with query strings or fragments', () => {
|
|
185
|
+
expect(basename('http://example.com/path/to/resource?query=param')).toBe('resource');
|
|
186
|
+
expect(basename('http://example.com/path/to/resource#section')).toBe('resource');
|
|
187
|
+
expect(basename('/path/to/resource?query=param')).toBe('resource');
|
|
188
|
+
expect(basename('/path/to/resource#section')).toBe('resource');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('handles URLs with special characters', () => {
|
|
192
|
+
expect(basename('http://example.com/path/to/resource%20name')).toBe('resource%20name');
|
|
193
|
+
expect(basename('/path/to/resource-name')).toBe('resource-name');
|
|
194
|
+
expect(basename('http://example.com/resource_name/')).toBe('resource_name');
|
|
195
|
+
expect(basename('/path/to/resource+name')).toBe('resource+name');
|
|
196
|
+
});
|
|
197
|
+
});
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Color } from '../color/index.js';
|
|
2
|
+
|
|
3
|
+
// Utility function to deep clone an object
|
|
4
|
+
export function deepClone<T>(obj: T): T {
|
|
5
|
+
const type = typeof obj;
|
|
6
|
+
if (type !== 'object') {
|
|
7
|
+
switch (type) {
|
|
8
|
+
case 'boolean':
|
|
9
|
+
case 'number':
|
|
10
|
+
case 'string':
|
|
11
|
+
case 'undefined':
|
|
12
|
+
return obj;
|
|
13
|
+
default: throw new Error(`Not implemented yet: "${type}" case`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (isSimpleObject(obj)) {
|
|
18
|
+
// @ts-expect-error: Too complicated to handle
|
|
19
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, deepClone(value)]));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (obj instanceof Array) {
|
|
23
|
+
// @ts-expect-error: Too complicated to handle
|
|
24
|
+
return obj.map((e: unknown) => deepClone(e));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (obj instanceof Color) {
|
|
28
|
+
return obj.clone() as T;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (obj == null) return obj;
|
|
32
|
+
|
|
33
|
+
console.log('obj', obj);
|
|
34
|
+
console.log('obj.prototype', Object.getPrototypeOf(obj));
|
|
35
|
+
throw Error();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isSimpleObject(item: unknown): item is object {
|
|
39
|
+
if (item === null) return false;
|
|
40
|
+
if (typeof item !== 'object') return false;
|
|
41
|
+
if (Array.isArray(item)) return false;
|
|
42
|
+
const prototypeKeyCount: number = Object.keys(Object.getPrototypeOf(item) as object).length;
|
|
43
|
+
if (prototypeKeyCount !== 0) return false;
|
|
44
|
+
if (item.constructor.name !== 'Object') return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isBasicType(item: unknown): item is boolean | number | string | undefined {
|
|
49
|
+
switch (typeof item) {
|
|
50
|
+
case 'boolean':
|
|
51
|
+
case 'number':
|
|
52
|
+
case 'string':
|
|
53
|
+
case 'undefined':
|
|
54
|
+
return true;
|
|
55
|
+
case 'object':
|
|
56
|
+
return false;
|
|
57
|
+
default:
|
|
58
|
+
throw Error('unknown type: ' + typeof item);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function deepMerge<T extends object>(source0: T, ...sources: Partial<T>[]): T {
|
|
63
|
+
const target: T = deepClone(source0);
|
|
64
|
+
|
|
65
|
+
for (const source of sources) {
|
|
66
|
+
if (typeof source !== 'object') continue;
|
|
67
|
+
for (const key in source) {
|
|
68
|
+
if (!(key in source)) continue;
|
|
69
|
+
|
|
70
|
+
const sourceValue = source[key] as T[typeof key];
|
|
71
|
+
|
|
72
|
+
// *********
|
|
73
|
+
// overwrite
|
|
74
|
+
// *********
|
|
75
|
+
switch (typeof sourceValue) {
|
|
76
|
+
case 'number':
|
|
77
|
+
case 'string':
|
|
78
|
+
case 'boolean':
|
|
79
|
+
target[key] = sourceValue;
|
|
80
|
+
continue;
|
|
81
|
+
default:
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isBasicType(target[key])) {
|
|
85
|
+
target[key] = deepClone(sourceValue);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (sourceValue instanceof Color) {
|
|
90
|
+
// @ts-expect-error: Too complicated to handle
|
|
91
|
+
target[key] = sourceValue.clone();
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
|
|
96
|
+
target[key] = deepMerge(target[key], sourceValue);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// *********
|
|
101
|
+
// merge
|
|
102
|
+
// *********
|
|
103
|
+
|
|
104
|
+
if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
|
|
105
|
+
target[key] = deepMerge(target[key], sourceValue);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log('target[key]:', target[key]);
|
|
110
|
+
console.log('source[key]:', source[key]);
|
|
111
|
+
throw Error('unpredicted case');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return target;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resolveUrl(base: string, url: string): string {
|
|
118
|
+
if (!base) return url;
|
|
119
|
+
url = new URL(url, base).href;
|
|
120
|
+
url = url.replace(/%7B/gi, '{');
|
|
121
|
+
url = url.replace(/%7D/gi, '}');
|
|
122
|
+
return url;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function basename(url: string): string {
|
|
126
|
+
if (!url) return '';
|
|
127
|
+
try {
|
|
128
|
+
url = new URL(url, 'http://example.org').pathname;
|
|
129
|
+
} catch (_) {
|
|
130
|
+
// ignore
|
|
131
|
+
}
|
|
132
|
+
url = url.replace(/\/+$/, '');
|
|
133
|
+
return url.split('/').pop() ?? '';
|
|
134
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FillLayerSpecification, SymbolLayerSpecification } from '@maplibre/maplibre-gl-style-spec';
|
|
2
|
+
import { getShortbreadLayers } from './layers.js';
|
|
3
|
+
import { Language } from '../style_builder/types.js';
|
|
4
|
+
|
|
5
|
+
describe('layers', () => {
|
|
6
|
+
it('should return an array of MaplibreLayer', () => {
|
|
7
|
+
const language: Language = 'en';
|
|
8
|
+
const layers = getShortbreadLayers({ language });
|
|
9
|
+
|
|
10
|
+
expect(Array.isArray(layers)).toBe(true);
|
|
11
|
+
expect(layers).not.toHaveLength(0);
|
|
12
|
+
layers.forEach((layer) => {
|
|
13
|
+
expect(layer).toHaveProperty('id');
|
|
14
|
+
expect(layer).toHaveProperty('type');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should handle language suffix correctly', () => {
|
|
19
|
+
const language: Language = 'en';
|
|
20
|
+
const layers = getShortbreadLayers({ language });
|
|
21
|
+
const labelLayer = layers.find((layer) => layer.id === 'label-street-pedestrian') as SymbolLayerSpecification;
|
|
22
|
+
|
|
23
|
+
expect(labelLayer).toBeDefined();
|
|
24
|
+
|
|
25
|
+
expect(labelLayer.layout?.['text-field']).toContain('{name_en}');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should create appropriate filters for land layers', () => {
|
|
29
|
+
const language: Language = 'en';
|
|
30
|
+
const layers = getShortbreadLayers({ language });
|
|
31
|
+
const landLayer = layers.find((layer) => layer.id === 'land-agriculture') as FillLayerSpecification;
|
|
32
|
+
|
|
33
|
+
expect(landLayer).toBeDefined();
|
|
34
|
+
expect(landLayer.filter).toEqual(['all', ['in', 'kind', 'brownfield', 'farmland', 'farmyard', 'greenfield', 'greenhouse_horticulture', 'orchard', 'plant_nursery', 'vineyard']]);
|
|
35
|
+
});
|
|
36
|
+
});
|