UNPKG

@casoon/dragonfly

Version:

Modular, lightweight CSS framework and design system for modern web projects. Optimized for Astro JS, LightningCSS and Container Queries with @layer-based architecture and comprehensive accessibility.

379 lines (316 loc) 11.9 kB
/** * Simple Test Suite for Dragonfly JavaScript Utilities * * Run this file in a browser console or Node.js environment to test utilities. * This is a lightweight testing approach without external dependencies. */ // Simple test framework class SimpleTest { constructor() { this.tests = []; this.passed = 0; this.failed = 0; } test(name, fn) { this.tests.push({ name, fn }); } async run() { for (const { name, fn } of this.tests) { try { await fn(); this.passed++; } catch (error) { console.error(`❌ ${name}: ${error.message}`); this.failed++; } } if (this.failed === 0) { } else { } } assert(condition, message = 'Assertion failed') { if (!condition) { throw new Error(message); } } assertEqual(actual, expected, message) { if (actual !== expected) { throw new Error(message || `Expected ${expected}, got ${actual}`); } } assertDeepEqual(actual, expected, message) { if (JSON.stringify(actual) !== JSON.stringify(expected)) { throw new Error( message || `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}` ); } } } // Test utilities (mock imports for testing) const testUtils = { // Mock implementations for testing debounce: (func, delay = 300) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), delay); }; }, sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)), capitalize: (str) => { if (!str || typeof str !== 'string') return str; return str.charAt(0).toUpperCase() + str.slice(1); }, formatDate: (dateStr, options = {}) => { const { locale = 'en-US', format = 'short' } = options; const date = new Date(dateStr); if (Number.isNaN(date.getTime())) { return 'Invalid Date'; } const formatOptions = { short: { year: 'numeric', month: 'short', day: 'numeric' }, medium: { year: 'numeric', month: 'long', day: 'numeric' }, long: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }, }; return date.toLocaleDateString(locale, formatOptions[format] || formatOptions.short); }, classNames: (...args) => { const classes = []; for (const arg of args) { if (!arg) continue; const argType = typeof arg; if (argType === 'string' || argType === 'number') { classes.push(arg); } else if (Array.isArray(arg)) { if (arg.length) { const inner = testUtils.classNames(...arg); if (inner) { classes.push(inner); } } } else if (argType === 'object') { for (const key in arg) { if (Object.hasOwn(arg, key) && arg[key]) { classes.push(key); } } } } return classes.join(' '); }, safeJsonParse: (str, fallback = {}) => { if (typeof str !== 'string') { return fallback; } try { const parsed = JSON.parse(str); return parsed !== null ? parsed : fallback; } catch (error) { return fallback; } }, isEmpty: (obj) => obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0 && obj.constructor === Object, uniqueArray: (arr, keyFn = null) => { if (!Array.isArray(arr)) return []; if (!keyFn) { return [...new Set(arr)]; } const seen = new Set(); const getKey = typeof keyFn === 'string' ? (item) => item[keyFn] : keyFn; return arr.filter((item) => { const key = getKey(item); if (seen.has(key)) { return false; } seen.add(key); return true; }); }, titleCase: (str) => { if (!str || typeof str !== 'string') return str; return str.replace( /\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() ); }, camelCase: (str) => { if (!str || typeof str !== 'string') return str; return str .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => index === 0 ? word.toLowerCase() : word.toUpperCase() ) .replace(/\s+/g, '') .replace(/[-_]/g, ''); }, truncate: (str, length, suffix = '...') => { if (!str || typeof str !== 'string') return str; if (str.length <= length) return str; return str.slice(0, length - suffix.length) + suffix; }, }; // Create test instance const test = new SimpleTest(); // String Helper Tests test.test('capitalize should capitalize first letter', () => { test.assertEqual(testUtils.capitalize('hello'), 'Hello'); test.assertEqual(testUtils.capitalize('HELLO'), 'HELLO'); test.assertEqual(testUtils.capitalize(''), ''); test.assertEqual(testUtils.capitalize(null), null); }); test.test('titleCase should capitalize each word', () => { test.assertEqual(testUtils.titleCase('hello world'), 'Hello World'); test.assertEqual(testUtils.titleCase('the quick brown fox'), 'The Quick Brown Fox'); test.assertEqual(testUtils.titleCase(''), ''); }); test.test('camelCase should convert to camelCase', () => { test.assertEqual(testUtils.camelCase('hello world'), 'helloWorld'); test.assertEqual(testUtils.camelCase('user-name'), 'userName'); test.assertEqual(testUtils.camelCase('first_name'), 'firstName'); }); test.test('truncate should truncate strings correctly', () => { test.assertEqual(testUtils.truncate('Hello World', 5), 'He...'); test.assertEqual(testUtils.truncate('Hi', 10), 'Hi'); test.assertEqual(testUtils.truncate('Hello World', 8, '***'), 'Hello***'); }); // Date Helper Tests test.test('formatDate should format dates correctly', () => { const date = '2024-04-22'; const formatted = testUtils.formatDate(date); test.assert(formatted.includes('2024'), 'Should include year'); test.assert(formatted.includes('Apr') || formatted.includes('22'), 'Should include month or day'); }); test.test('formatDate should handle invalid dates', () => { test.assertEqual(testUtils.formatDate('invalid-date'), 'Invalid Date'); test.assertEqual(testUtils.formatDate(''), 'Invalid Date'); }); // ClassNames Tests test.test('classNames should join strings', () => { test.assertEqual(testUtils.classNames('btn', 'primary'), 'btn primary'); test.assertEqual(testUtils.classNames('btn'), 'btn'); }); test.test('classNames should handle conditional classes', () => { test.assertEqual(testUtils.classNames('btn', true && 'active'), 'btn active'); test.assertEqual(testUtils.classNames('btn', false && 'active'), 'btn'); }); test.test('classNames should handle object syntax', () => { test.assertEqual(testUtils.classNames('btn', { active: true, disabled: false }), 'btn active'); }); test.test('classNames should handle arrays', () => { test.assertEqual(testUtils.classNames(['btn', 'large'], 'active'), 'btn large active'); }); // JSON Helper Tests test.test('safeJsonParse should parse valid JSON', () => { test.assertDeepEqual(testUtils.safeJsonParse('{"name":"John"}'), { name: 'John' }); test.assertDeepEqual(testUtils.safeJsonParse('[1,2,3]'), [1, 2, 3]); }); test.test('safeJsonParse should return fallback for invalid JSON', () => { test.assertDeepEqual(testUtils.safeJsonParse('invalid json'), {}); test.assertDeepEqual(testUtils.safeJsonParse('invalid json', []), []); test.assertDeepEqual(testUtils.safeJsonParse(null), {}); }); // Object Helper Tests test.test('isEmpty should detect empty objects', () => { test.assert(testUtils.isEmpty({}), 'Empty object should be empty'); test.assert(!testUtils.isEmpty({ name: 'John' }), 'Object with properties should not be empty'); test.assert(!testUtils.isEmpty([]), 'Array should not be empty (not plain object)'); test.assert(!testUtils.isEmpty(null), 'null should not be empty'); test.assert(!testUtils.isEmpty(undefined), 'undefined should not be empty'); }); // Array Helper Tests test.test('uniqueArray should remove duplicates', () => { test.assertDeepEqual(testUtils.uniqueArray([1, 2, 2, 3, 1]), [1, 2, 3]); test.assertDeepEqual(testUtils.uniqueArray(['a', 'b', 'a']), ['a', 'b']); test.assertDeepEqual(testUtils.uniqueArray([]), []); }); test.test('uniqueArray should work with key function', () => { const users = [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 1, name: 'John Doe' }, ]; const unique = testUtils.uniqueArray(users, 'id'); test.assertEqual(unique.length, 2, 'Should have 2 unique users'); test.assertEqual(unique[0].id, 1, 'First user should have id 1'); test.assertEqual(unique[1].id, 2, 'Second user should have id 2'); }); test.test('uniqueArray should handle non-arrays', () => { test.assertDeepEqual(testUtils.uniqueArray(null), []); test.assertDeepEqual(testUtils.uniqueArray(undefined), []); test.assertDeepEqual(testUtils.uniqueArray('string'), []); }); // Async Helper Tests test.test('sleep should delay execution', async () => { const start = Date.now(); await testUtils.sleep(100); const end = Date.now(); const elapsed = end - start; test.assert(elapsed >= 90, `Sleep should take at least 90ms, took ${elapsed}ms`); test.assert(elapsed < 200, `Sleep should take less than 200ms, took ${elapsed}ms`); }); test.test('debounce should delay function execution', async () => { let callCount = 0; const fn = () => callCount++; const debouncedFn = testUtils.debounce(fn, 50); // Call multiple times quickly debouncedFn(); debouncedFn(); debouncedFn(); // Should not have been called yet test.assertEqual(callCount, 0, 'Function should not be called immediately'); // Wait for debounce delay await testUtils.sleep(60); // Should have been called once test.assertEqual(callCount, 1, 'Function should be called once after delay'); }); // Performance Tests test.test('utilities should handle large datasets', () => { // Test with large array const largeArray = Array.from({ length: 10000 }, (_, i) => i % 100); const start = Date.now(); const unique = testUtils.uniqueArray(largeArray); const end = Date.now(); test.assertEqual(unique.length, 100, 'Should have 100 unique values'); test.assert(end - start < 100, `Should complete in under 100ms, took ${end - start}ms`); }); test.test('classNames should handle many arguments', () => { const manyClasses = Array.from({ length: 100 }, (_, i) => `class-${i}`); const result = testUtils.classNames(...manyClasses); test.assert(result.includes('class-0'), 'Should include first class'); test.assert(result.includes('class-99'), 'Should include last class'); test.assertEqual(result.split(' ').length, 100, 'Should have 100 classes'); }); // Edge Case Tests test.test('utilities should handle edge cases gracefully', () => { // Test with null/undefined inputs test.assertEqual(testUtils.capitalize(null), null); test.assertEqual(testUtils.capitalize(undefined), undefined); test.assertEqual(testUtils.classNames(null, undefined, false, ''), ''); test.assertDeepEqual(testUtils.uniqueArray(null), []); // Test with empty inputs test.assertEqual(testUtils.capitalize(''), ''); test.assertEqual(testUtils.classNames(), ''); test.assertDeepEqual(testUtils.uniqueArray([]), []); }); // Export for use in browser or Node.js if (typeof module !== 'undefined' && module.exports) { module.exports = { test, testUtils }; } else if (typeof window !== 'undefined') { window.DragonflyTests = { test, testUtils }; } // Auto-run tests if this file is executed directly if (typeof window !== 'undefined') { // Browser environment - run tests when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => test.run()); } else { test.run(); } } else if (typeof require !== 'undefined' && require.main === module) { // Node.js environment - run tests immediately test.run(); }