diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e983a..f37f425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v1.0.1] - 2026-01-05 + +### Added +- Add tests for `escapeCSS()` +- This release will add Package Provenance + ## [v1.0.0] - 2026-01-05 Initial Release diff --git a/css.test.js b/css.test.js new file mode 100644 index 0000000..9391e54 --- /dev/null +++ b/css.test.js @@ -0,0 +1,71 @@ +import assert from 'node:assert'; +import { describe, test } from 'node:test'; +import { escapeCSS } from './css.js'; + +describe('escapeCSS', () => { + test('passes through valid identifiers', () => { + assert.strictEqual(escapeCSS('foo'), 'foo'); + assert.strictEqual(escapeCSS('foo-bar'), 'foo-bar'); + assert.strictEqual(escapeCSS('foo_bar'), 'foo_bar'); + assert.strictEqual(escapeCSS('FOO'), 'FOO'); + }); + + test('escapes syntax characters', () => { + // Class, ID, Pseudo-classes + assert.strictEqual(escapeCSS('foo.bar'), 'foo\\.bar'); + assert.strictEqual(escapeCSS('foo#bar'), 'foo\\#bar'); + assert.strictEqual(escapeCSS('foo:bar'), 'foo\\:bar'); + // Brackets and attributes + assert.strictEqual(escapeCSS('[data=val]'), '\\[data\\=val\\]'); + }); + + test('handles leading digits (must be hex escaped)', () => { + // 1 -> \31 + space + assert.strictEqual(escapeCSS('123'), '\\31 23'); + // 9 -> \39 + space + assert.strictEqual(escapeCSS('987'), '\\39 87'); + // 0 -> \30 + space + assert.strictEqual(escapeCSS('0abc'), '\\30 abc'); + }); + + test('handles leading hyphen followed by digit', () => { + // Hyphen remains, digit becomes hex escaped + assert.strictEqual(escapeCSS('-123'), '-\\31 23'); + assert.strictEqual(escapeCSS('-0'), '-\\30 '); + }); + + test('handles single hyphen vs double hyphen', () => { + // Single hyphen is a reserved syntax if standalone, must escape + assert.strictEqual(escapeCSS('-'), '\\-'); + + // Double hyphen is valid (starts a custom property), passes through + assert.strictEqual(escapeCSS('--var'), '--var'); + }); + + test('handles control characters', () => { + // Null byte -> Replacement Character (U+FFFD) + assert.strictEqual(escapeCSS('\u0000'), '\uFFFD'); + + // C0 Control (0x01-0x1F) -> Hex escape + space + assert.strictEqual(escapeCSS('\x01'), '\\1 '); + assert.strictEqual(escapeCSS('\x1F'), '\\1f '); + + // Delete (0x7F) -> Hex escape + space + assert.strictEqual(escapeCSS('\x7F'), '\\7f '); + }); + + test('handles high ASCII and Unicode', () => { + // Should pass through untouched + assert.strictEqual(escapeCSS('©'), '©'); + assert.strictEqual(escapeCSS('💩'), '💩'); + assert.strictEqual(escapeCSS('über'), 'über'); + }); + + test('converts non-strings to strings (IDL behavior)', () => { + assert.strictEqual(escapeCSS(123), '\\31 23'); + // String(null) -> "null" (valid identifier) + assert.strictEqual(escapeCSS(null), 'null'); + // String(undefined) -> "undefined" (valid identifier) + assert.strictEqual(escapeCSS(undefined), 'undefined'); + }); +}); diff --git a/package-lock.json b/package-lock.json index 526549c..c221a1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "npm-template", - "version": "1.1.3", + "name": "@aegisjsproject/escape", + "version": "1.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "npm-template", - "version": "1.1.3", + "name": "@aegisjsproject/escape", + "version": "1.0.1", "funding": [ { "type": "librepay", diff --git a/package.json b/package.json index b30ab1f..1c254f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aegisjsproject/escape", - "version": "1.0.0", + "version": "1.0.1", "description": "String escaping utilities for HTML and DOM attributes.", "keywords": [ "security",