@alepot55/chessboardjs 2.2.0 → 2.3.2
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/.eslintrc.json +227 -0
- package/.github/instructions/copilot-instuctions.md +1671 -0
- package/README.md +127 -403
- package/assets/themes/alepot/theme.json +42 -0
- package/assets/themes/default/theme.json +42 -0
- package/chessboard.bundle.js +782 -119
- package/config/jest.config.js +15 -0
- package/config/rollup.config.js +35 -0
- package/dist/chessboard.cjs.js +10476 -0
- package/dist/chessboard.css +197 -0
- package/dist/chessboard.esm.js +10407 -0
- package/dist/chessboard.iife.js +10481 -0
- package/dist/chessboard.umd.js +10482 -0
- package/jest.config.js +2 -7
- package/package.json +18 -3
- package/rollup.config.js +2 -11
- package/{chessboard.move.js → src/components/Move.js} +3 -3
- package/src/components/Piece.js +273 -0
- package/{chessboard.square.js → src/components/Square.js} +60 -7
- package/src/constants/index.js +15 -0
- package/src/constants/positions.js +62 -0
- package/src/core/Chessboard.js +1930 -0
- package/src/core/ChessboardConfig.js +458 -0
- package/src/core/ChessboardFactory.js +385 -0
- package/src/core/index.js +141 -0
- package/src/errors/ChessboardError.js +133 -0
- package/src/errors/index.js +15 -0
- package/src/errors/messages.js +189 -0
- package/src/index.js +103 -0
- package/src/services/AnimationService.js +180 -0
- package/src/services/BoardService.js +156 -0
- package/src/services/CoordinateService.js +355 -0
- package/src/services/EventService.js +807 -0
- package/src/services/MoveService.js +594 -0
- package/src/services/PieceService.js +303 -0
- package/src/services/PositionService.js +237 -0
- package/src/services/ValidationService.js +673 -0
- package/src/services/index.js +14 -0
- package/src/styles/animations.css +46 -0
- package/{chessboard.css → src/styles/board.css} +3 -0
- package/src/styles/index.css +4 -0
- package/src/styles/pieces.css +66 -0
- package/src/utils/animations.js +37 -0
- package/{chess.js → src/utils/chess.js} +16 -16
- package/src/utils/coordinates.js +62 -0
- package/src/utils/cross-browser.js +150 -0
- package/src/utils/logger.js +422 -0
- package/src/utils/performance.js +311 -0
- package/src/utils/validation.js +458 -0
- package/tests/unit/chessboard-config-animations.test.js +106 -0
- package/tests/unit/chessboard-robust.test.js +163 -0
- package/tests/unit/chessboard.test.js +183 -0
- package/chessboard.config.js +0 -147
- package/chessboard.js +0 -981
- package/chessboard.piece.js +0 -115
- package/test/chessboard.test.js +0 -128
- /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
- /package/{.babelrc → config/.babelrc} +0 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities for Chessboard.js
|
|
3
|
+
* @module utils/validation
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Re-export from coordinates utility
|
|
8
|
+
export { isValidSquare } from './coordinates.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validation cache for performance optimization
|
|
12
|
+
* @type {Map}
|
|
13
|
+
*/
|
|
14
|
+
const validationCache = new Map();
|
|
15
|
+
const CACHE_MAX_SIZE = 1000;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Caches validation result
|
|
19
|
+
* @param {string} key - Cache key
|
|
20
|
+
* @param {boolean} result - Validation result
|
|
21
|
+
*/
|
|
22
|
+
function cacheResult(key, result) {
|
|
23
|
+
if (validationCache.size >= CACHE_MAX_SIZE) {
|
|
24
|
+
const firstKey = validationCache.keys().next().value;
|
|
25
|
+
validationCache.delete(firstKey);
|
|
26
|
+
}
|
|
27
|
+
validationCache.set(key, result);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate piece notation with caching
|
|
32
|
+
* @param {string} piece - Piece notation (e.g., 'wP', 'bK')
|
|
33
|
+
* @returns {boolean} True if piece notation is valid
|
|
34
|
+
*/
|
|
35
|
+
export function isValidPiece(piece) {
|
|
36
|
+
if (typeof piece !== 'string' || piece.length !== 2) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cacheKey = `piece:${piece}`;
|
|
41
|
+
if (validationCache.has(cacheKey)) {
|
|
42
|
+
return validationCache.get(cacheKey);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const color = piece[0];
|
|
46
|
+
const type = piece[1];
|
|
47
|
+
|
|
48
|
+
const isValid = ['w', 'b'].includes(color) &&
|
|
49
|
+
['P', 'R', 'N', 'B', 'Q', 'K'].includes(type);
|
|
50
|
+
|
|
51
|
+
cacheResult(cacheKey, isValid);
|
|
52
|
+
return isValid;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate position object with comprehensive checks
|
|
57
|
+
* @param {Object} position - Position object with square-piece mappings
|
|
58
|
+
* @returns {boolean} True if position is valid
|
|
59
|
+
*/
|
|
60
|
+
export function isValidPosition(position) {
|
|
61
|
+
if (typeof position !== 'object' || position === null) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for circular references
|
|
66
|
+
try {
|
|
67
|
+
JSON.stringify(position);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Validate each square-piece mapping
|
|
73
|
+
for (const [square, piece] of Object.entries(position)) {
|
|
74
|
+
if (!isValidSquare(square) || !isValidPiece(piece)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Optional: Check for chess-specific rules
|
|
80
|
+
return isValidChessPosition(position);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validates chess-specific position rules
|
|
85
|
+
* @param {Object} position - Position object
|
|
86
|
+
* @returns {boolean} True if position follows chess rules
|
|
87
|
+
*/
|
|
88
|
+
function isValidChessPosition(position) {
|
|
89
|
+
const pieces = Object.values(position);
|
|
90
|
+
|
|
91
|
+
// Count kings
|
|
92
|
+
const whiteKings = pieces.filter(p => p === 'wK').length;
|
|
93
|
+
const blackKings = pieces.filter(p => p === 'bK').length;
|
|
94
|
+
|
|
95
|
+
// Must have exactly one king of each color
|
|
96
|
+
if (whiteKings !== 1 || blackKings !== 1) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Count pawns (max 8 per side)
|
|
101
|
+
const whitePawns = pieces.filter(p => p === 'wP').length;
|
|
102
|
+
const blackPawns = pieces.filter(p => p === 'bP').length;
|
|
103
|
+
|
|
104
|
+
if (whitePawns > 8 || blackPawns > 8) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check pawn placement (not on first or last rank)
|
|
109
|
+
for (const [square, piece] of Object.entries(position)) {
|
|
110
|
+
if (piece === 'wP' || piece === 'bP') {
|
|
111
|
+
const rank = square[1];
|
|
112
|
+
if (rank === '1' || rank === '8') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate FEN string format with comprehensive checks
|
|
123
|
+
* @param {string} fen - FEN string
|
|
124
|
+
* @returns {Object} Validation result with success and error properties
|
|
125
|
+
*/
|
|
126
|
+
export function validateFenFormat(fen) {
|
|
127
|
+
if (typeof fen !== 'string') {
|
|
128
|
+
return { success: false, error: 'FEN must be a string' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const cacheKey = `fen:${fen}`;
|
|
132
|
+
if (validationCache.has(cacheKey)) {
|
|
133
|
+
return validationCache.get(cacheKey);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const parts = fen.trim().split(' ');
|
|
137
|
+
if (parts.length !== 6) {
|
|
138
|
+
const result = { success: false, error: 'FEN must have 6 parts separated by spaces' };
|
|
139
|
+
cacheResult(cacheKey, result);
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Validate piece placement
|
|
144
|
+
const ranks = parts[0].split('/');
|
|
145
|
+
if (ranks.length !== 8) {
|
|
146
|
+
const result = { success: false, error: 'Piece placement must have 8 ranks' };
|
|
147
|
+
cacheResult(cacheKey, result);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Validate each rank
|
|
152
|
+
for (let i = 0; i < ranks.length; i++) {
|
|
153
|
+
const rankResult = validateRank(ranks[i]);
|
|
154
|
+
if (!rankResult.success) {
|
|
155
|
+
const result = { success: false, error: `Invalid rank ${i + 1}: ${rankResult.error}` };
|
|
156
|
+
cacheResult(cacheKey, result);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Validate active color
|
|
162
|
+
if (!['w', 'b'].includes(parts[1])) {
|
|
163
|
+
const result = { success: false, error: 'Active color must be "w" or "b"' };
|
|
164
|
+
cacheResult(cacheKey, result);
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate castling availability
|
|
169
|
+
if (!/^[KQkq-]*$/.test(parts[2])) {
|
|
170
|
+
const result = { success: false, error: 'Invalid castling availability' };
|
|
171
|
+
cacheResult(cacheKey, result);
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Validate en passant target square
|
|
176
|
+
if (parts[3] !== '-' && !isValidSquare(parts[3])) {
|
|
177
|
+
const result = { success: false, error: 'Invalid en passant target square' };
|
|
178
|
+
cacheResult(cacheKey, result);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Validate halfmove clock
|
|
183
|
+
const halfmove = parseInt(parts[4], 10);
|
|
184
|
+
if (isNaN(halfmove) || halfmove < 0) {
|
|
185
|
+
const result = { success: false, error: 'Invalid halfmove clock' };
|
|
186
|
+
cacheResult(cacheKey, result);
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Validate fullmove number
|
|
191
|
+
const fullmove = parseInt(parts[5], 10);
|
|
192
|
+
if (isNaN(fullmove) || fullmove < 1) {
|
|
193
|
+
const result = { success: false, error: 'Invalid fullmove number' };
|
|
194
|
+
cacheResult(cacheKey, result);
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = { success: true };
|
|
199
|
+
cacheResult(cacheKey, result);
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Validates a single rank in FEN notation
|
|
205
|
+
* @param {string} rank - Rank string
|
|
206
|
+
* @returns {Object} Validation result
|
|
207
|
+
*/
|
|
208
|
+
function validateRank(rank) {
|
|
209
|
+
if (typeof rank !== 'string') {
|
|
210
|
+
return { success: false, error: 'Rank must be a string' };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let squareCount = 0;
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < rank.length; i++) {
|
|
216
|
+
const char = rank[i];
|
|
217
|
+
|
|
218
|
+
if (/[1-8]/.test(char)) {
|
|
219
|
+
squareCount += parseInt(char, 10);
|
|
220
|
+
} else if (/[prnbqkPRNBQK]/.test(char)) {
|
|
221
|
+
squareCount += 1;
|
|
222
|
+
} else {
|
|
223
|
+
return { success: false, error: `Invalid character "${char}" in rank` };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (squareCount !== 8) {
|
|
228
|
+
return { success: false, error: `Rank must represent exactly 8 squares, got ${squareCount}` };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { success: true };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Validate move notation in various formats
|
|
236
|
+
* @param {string} move - Move string (e.g., 'e2e4', 'Nf3', 'O-O')
|
|
237
|
+
* @returns {Object} Validation result
|
|
238
|
+
*/
|
|
239
|
+
export function validateMove(move) {
|
|
240
|
+
if (typeof move !== 'string') {
|
|
241
|
+
return { success: false, error: 'Move must be a string' };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const trimmed = move.trim();
|
|
245
|
+
if (trimmed.length === 0) {
|
|
246
|
+
return { success: false, error: 'Move cannot be empty' };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check for castling
|
|
250
|
+
if (trimmed === 'O-O' || trimmed === 'O-O-O') {
|
|
251
|
+
return { success: true, type: 'castling' };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check for coordinate notation (e.g., e2e4, e7e8q)
|
|
255
|
+
if (/^[a-h][1-8][a-h][1-8][qrnbQRNB]?$/.test(trimmed)) {
|
|
256
|
+
return { success: true, type: 'coordinate' };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Check for algebraic notation (e.g., Nf3, Qxd5, e4)
|
|
260
|
+
if (/^[PRNBQK]?[a-h]?[1-8]?x?[a-h][1-8](\+|#)?$/.test(trimmed)) {
|
|
261
|
+
return { success: true, type: 'algebraic' };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { success: false, error: 'Invalid move format' };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Validate configuration object with detailed error reporting
|
|
269
|
+
* @param {Object} config - Configuration object
|
|
270
|
+
* @returns {Object} Validation result with success and errors array
|
|
271
|
+
*/
|
|
272
|
+
export function validateConfig(config) {
|
|
273
|
+
const errors = [];
|
|
274
|
+
|
|
275
|
+
if (!config || typeof config !== 'object') {
|
|
276
|
+
return { success: false, errors: ['Configuration must be an object'] };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Required fields
|
|
280
|
+
if (!config.id && !config.id_div) {
|
|
281
|
+
errors.push('Configuration must include "id" or "id_div"');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Optional field validation
|
|
285
|
+
if (config.orientation && !['white', 'black', 'w', 'b'].includes(config.orientation)) {
|
|
286
|
+
errors.push('Invalid orientation. Must be "white", "black", "w", or "b"');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (config.position && config.position !== 'start' && typeof config.position !== 'object') {
|
|
290
|
+
errors.push('Invalid position. Must be "start" or a position object');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (config.position && typeof config.position === 'object' && !isValidPosition(config.position)) {
|
|
294
|
+
errors.push('Invalid position object format');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (config.size && !isValidSize(config.size)) {
|
|
298
|
+
errors.push('Invalid size. Must be "auto", a positive number, or a valid CSS size');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (config.movableColors && !['white', 'black', 'w', 'b', 'both', 'none'].includes(config.movableColors)) {
|
|
302
|
+
errors.push('Invalid movableColors. Must be "white", "black", "w", "b", "both", or "none"');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (config.dropOffBoard && !['snapback', 'trash'].includes(config.dropOffBoard)) {
|
|
306
|
+
errors.push('Invalid dropOffBoard. Must be "snapback" or "trash"');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Validate callback functions
|
|
310
|
+
const callbacks = ['onMove', 'onMoveEnd', 'onChange', 'onDragStart', 'onDragMove', 'onDrop', 'onSnapbackEnd'];
|
|
311
|
+
for (const callback of callbacks) {
|
|
312
|
+
if (config[callback] && typeof config[callback] !== 'function') {
|
|
313
|
+
errors.push(`Invalid ${callback}. Must be a function`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Validate color values
|
|
318
|
+
const colorFields = ['whiteSquare', 'blackSquare', 'highlight', 'hintColor'];
|
|
319
|
+
for (const field of colorFields) {
|
|
320
|
+
if (config[field] && !isValidColor(config[field])) {
|
|
321
|
+
errors.push(`Invalid ${field}. Must be a valid CSS color`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Validate animation settings
|
|
326
|
+
if (config.moveAnimation && !isValidEasing(config.moveAnimation)) {
|
|
327
|
+
errors.push('Invalid moveAnimation. Must be a valid easing function');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (config.animationStyle && !['sequential', 'simultaneous'].includes(config.animationStyle)) {
|
|
331
|
+
errors.push('Invalid animationStyle. Must be "sequential" or "simultaneous"');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
success: errors.length === 0,
|
|
336
|
+
errors
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Validates size value
|
|
342
|
+
* @param {number|string} size - Size value
|
|
343
|
+
* @returns {boolean} True if valid
|
|
344
|
+
*/
|
|
345
|
+
function isValidSize(size) {
|
|
346
|
+
if (size === 'auto') {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (typeof size === 'number') {
|
|
351
|
+
return size > 0 && size <= 5000; // Reasonable upper limit
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (typeof size === 'string') {
|
|
355
|
+
// Check for CSS size values
|
|
356
|
+
return /^\d+(px|em|rem|%|vh|vw)$/.test(size);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Validates CSS color value
|
|
364
|
+
* @param {string} color - Color value
|
|
365
|
+
* @returns {boolean} True if valid
|
|
366
|
+
*/
|
|
367
|
+
function isValidColor(color) {
|
|
368
|
+
if (typeof color !== 'string') {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Hex colors
|
|
373
|
+
if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color)) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// RGB/RGBA
|
|
378
|
+
if (/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*[0-1](\.\d+)?)?\s*\)$/.test(color)) {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// HSL/HSLA
|
|
383
|
+
if (/^hsla?\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*(,\s*[0-1](\.\d+)?)?\s*\)$/.test(color)) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Named colors (basic set)
|
|
388
|
+
const namedColors = [
|
|
389
|
+
'white', 'black', 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta',
|
|
390
|
+
'transparent', 'gray', 'grey', 'brown', 'orange', 'purple', 'pink'
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
return namedColors.includes(color.toLowerCase());
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Validates easing function
|
|
398
|
+
* @param {string} easing - Easing function name
|
|
399
|
+
* @returns {boolean} True if valid
|
|
400
|
+
*/
|
|
401
|
+
function isValidEasing(easing) {
|
|
402
|
+
const validEasing = [
|
|
403
|
+
'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out',
|
|
404
|
+
'cubic-bezier'
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
return validEasing.includes(easing) ||
|
|
408
|
+
/^cubic-bezier\(\s*[\d.-]+\s*,\s*[\d.-]+\s*,\s*[\d.-]+\s*,\s*[\d.-]+\s*\)$/.test(easing);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Batch validates multiple items
|
|
413
|
+
* @param {Array} items - Array of validation items
|
|
414
|
+
* @returns {Array} Array of validation results
|
|
415
|
+
*/
|
|
416
|
+
export function batchValidate(items) {
|
|
417
|
+
return items.map(item => {
|
|
418
|
+
const { type, value } = item;
|
|
419
|
+
|
|
420
|
+
switch (type) {
|
|
421
|
+
case 'piece':
|
|
422
|
+
return { ...item, valid: isValidPiece(value) };
|
|
423
|
+
case 'square':
|
|
424
|
+
return { ...item, valid: isValidSquare(value) };
|
|
425
|
+
case 'position':
|
|
426
|
+
return { ...item, valid: isValidPosition(value) };
|
|
427
|
+
case 'fen':
|
|
428
|
+
const fenResult = validateFenFormat(value);
|
|
429
|
+
return { ...item, valid: fenResult.success, error: fenResult.error };
|
|
430
|
+
case 'move':
|
|
431
|
+
const moveResult = validateMove(value);
|
|
432
|
+
return { ...item, valid: moveResult.success, error: moveResult.error };
|
|
433
|
+
case 'config':
|
|
434
|
+
const configResult = validateConfig(value);
|
|
435
|
+
return { ...item, valid: configResult.success, errors: configResult.errors };
|
|
436
|
+
default:
|
|
437
|
+
return { ...item, valid: false, error: 'Unknown validation type' };
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Clears validation cache
|
|
444
|
+
*/
|
|
445
|
+
export function clearValidationCache() {
|
|
446
|
+
validationCache.clear();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Gets validation cache statistics
|
|
451
|
+
* @returns {Object} Cache statistics
|
|
452
|
+
*/
|
|
453
|
+
export function getValidationCacheStats() {
|
|
454
|
+
return {
|
|
455
|
+
size: validationCache.size,
|
|
456
|
+
maxSize: CACHE_MAX_SIZE
|
|
457
|
+
};
|
|
458
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ChessboardConfig } from '../../src/core/ChessboardConfig.js';
|
|
2
|
+
|
|
3
|
+
describe('ChessboardConfig Animation & Drag Properties', () => {
|
|
4
|
+
const baseConfig = { id: 'test-board' };
|
|
5
|
+
|
|
6
|
+
test('should handle boolean moveAnimation values', () => {
|
|
7
|
+
expect(() => {
|
|
8
|
+
new ChessboardConfig({ ...baseConfig, moveAnimation: true });
|
|
9
|
+
}).not.toThrow();
|
|
10
|
+
expect(() => {
|
|
11
|
+
new ChessboardConfig({ ...baseConfig, moveAnimation: false });
|
|
12
|
+
}).not.toThrow();
|
|
13
|
+
// Check resulting property
|
|
14
|
+
expect(new ChessboardConfig({ ...baseConfig, moveAnimation: true }).moveAnimation).toBe('ease');
|
|
15
|
+
expect(new ChessboardConfig({ ...baseConfig, moveAnimation: false }).moveAnimation).toBe(null);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should handle boolean snapbackAnimation values', () => {
|
|
19
|
+
expect(() => {
|
|
20
|
+
new ChessboardConfig({ ...baseConfig, snapbackAnimation: true });
|
|
21
|
+
}).not.toThrow();
|
|
22
|
+
expect(() => {
|
|
23
|
+
new ChessboardConfig({ ...baseConfig, snapbackAnimation: false });
|
|
24
|
+
}).not.toThrow();
|
|
25
|
+
expect(new ChessboardConfig({ ...baseConfig, snapbackAnimation: true }).snapbackAnimation).toBe('ease');
|
|
26
|
+
expect(new ChessboardConfig({ ...baseConfig, snapbackAnimation: false }).snapbackAnimation).toBe(null);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('should handle boolean fadeAnimation values', () => {
|
|
30
|
+
expect(() => {
|
|
31
|
+
new ChessboardConfig({ ...baseConfig, fadeAnimation: true });
|
|
32
|
+
}).not.toThrow();
|
|
33
|
+
expect(() => {
|
|
34
|
+
new ChessboardConfig({ ...baseConfig, fadeAnimation: false });
|
|
35
|
+
}).not.toThrow();
|
|
36
|
+
expect(new ChessboardConfig({ ...baseConfig, fadeAnimation: true }).fadeAnimation).toBe('ease');
|
|
37
|
+
expect(new ChessboardConfig({ ...baseConfig, fadeAnimation: false }).fadeAnimation).toBe(null);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should handle string animation values', () => {
|
|
41
|
+
expect(() => {
|
|
42
|
+
new ChessboardConfig({
|
|
43
|
+
...baseConfig,
|
|
44
|
+
moveAnimation: 'ease-in-out',
|
|
45
|
+
snapbackAnimation: 'linear',
|
|
46
|
+
fadeAnimation: 'ease'
|
|
47
|
+
});
|
|
48
|
+
}).not.toThrow();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should handle none animation values', () => {
|
|
52
|
+
expect(() => {
|
|
53
|
+
new ChessboardConfig({
|
|
54
|
+
...baseConfig,
|
|
55
|
+
moveAnimation: 'none',
|
|
56
|
+
snapbackAnimation: 'none',
|
|
57
|
+
fadeAnimation: 'none'
|
|
58
|
+
});
|
|
59
|
+
}).not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('should throw error for invalid animation values', () => {
|
|
63
|
+
expect(() => {
|
|
64
|
+
new ChessboardConfig({ ...baseConfig, moveAnimation: 'invalid-animation' });
|
|
65
|
+
}).toThrow('Invalid transition function');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// --- DRAGGABLE ---
|
|
69
|
+
test('should handle draggable true/false', () => {
|
|
70
|
+
expect(() => new ChessboardConfig({ ...baseConfig, draggable: true })).not.toThrow();
|
|
71
|
+
expect(() => new ChessboardConfig({ ...baseConfig, draggable: false })).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
test('should default draggable to true', () => {
|
|
74
|
+
const config = new ChessboardConfig(baseConfig);
|
|
75
|
+
expect(config.draggable).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
test('should throw error for invalid draggable value', () => {
|
|
78
|
+
expect(() => new ChessboardConfig({ ...baseConfig, draggable: 'maybe' })).toThrow();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// --- DROP OFF BOARD ---
|
|
82
|
+
test('should accept valid dropOffBoard values', () => {
|
|
83
|
+
expect(() => new ChessboardConfig({ ...baseConfig, dropOffBoard: 'snapback' })).not.toThrow();
|
|
84
|
+
expect(() => new ChessboardConfig({ ...baseConfig, dropOffBoard: 'trash' })).not.toThrow();
|
|
85
|
+
});
|
|
86
|
+
test('should throw error for invalid dropOffBoard value', () => {
|
|
87
|
+
expect(() => new ChessboardConfig({ ...baseConfig, dropOffBoard: 'invalid' })).toThrow();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// --- ANIMATION STYLE ---
|
|
91
|
+
test('should accept valid animationStyle values', () => {
|
|
92
|
+
expect(() => new ChessboardConfig({ ...baseConfig, animationStyle: 'simultaneous' })).not.toThrow();
|
|
93
|
+
expect(() => new ChessboardConfig({ ...baseConfig, animationStyle: 'sequential' })).not.toThrow();
|
|
94
|
+
});
|
|
95
|
+
test('should throw error for invalid animationStyle value', () => {
|
|
96
|
+
expect(() => new ChessboardConfig({ ...baseConfig, animationStyle: 'crazy' })).toThrow();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// --- EDGE CASES ---
|
|
100
|
+
test('should throw error if id/id_div is missing', () => {
|
|
101
|
+
expect(() => new ChessboardConfig({})).toThrow();
|
|
102
|
+
});
|
|
103
|
+
test('should throw error for non-object config', () => {
|
|
104
|
+
expect(() => new ChessboardConfig('not-an-object')).toThrow();
|
|
105
|
+
});
|
|
106
|
+
});
|