diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..0388d89
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,73 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+- **DOMTable class** for creating HTML table structures
+ - `createTable()` - Create table element
+ - `createTableHead()` - Create thead element
+ - `createTableBody()` - Create tbody element
+ - `createTableFoot()` - Create tfoot element
+ - `createTableRow()` - Create tr element
+ - `createTableHeader()` - Create th element with scope support
+ - `createTableCell()` - Create td element with colspan/rowspan support
+ - `createCaption()` - Create caption element
+
+- **DOMMedia class** for creating media elements
+ - `createImage()` - Create img element with src, alt, width, height, and loading attributes
+
+- **DOMSemantic class** for creating semantic HTML5 elements
+ - `createArticle()` - Create article element
+ - `createSection()` - Create section element
+ - `createNav()` - Create nav element
+ - `createHeader()` - Create header element
+ - `createFooter()` - Create footer element
+ - `createAside()` - Create aside element
+ - `createMain()` - Create main element
+
+- **Enhanced DOMNested class** with additional list types
+ - `createOrderedList()` - Create ol element
+ - `createDefinitionList()` - Create dl element
+ - `createDefinitionTerm()` - Create dt element
+ - `createDefinitionDescription()` - Create dd element
+
+- **Enhanced DOMInput class** with select dropdown support
+ - `createSelect()` - Create select element with onChange handler support
+ - `createOption()` - Create option element with value and selected attributes
+ - `createOptgroup()` - Create optgroup element for grouping options
+
+- **Example plugin** (`examples/dom_elements_plugin.ts`) demonstrating all new DOM element capabilities
+
+- **Comprehensive test coverage** for all new DOM classes and methods
+ - Unit tests for DOMTable, DOMMedia, DOMSemantic
+ - Enhanced tests for DOMNested and DOMInput
+ - Integration tests demonstrating real-world usage
+
+### Changed
+
+- Updated README.md with comprehensive documentation of new DOM element capabilities
+- Updated index.ts to export new DOM classes (DOMTable, DOMMedia, DOMSemantic)
+
+### Technical Details
+
+- All new DOM classes extend the base DOM class and follow existing architecture patterns
+- Full TypeScript type safety with strict mode enabled
+- JSDoc documentation with @uiName tags for all public methods
+- CSS-in-JS integration via goober for styling support
+- Custom attributes support for all new elements
+- Event handler support for interactive elements
+- Maintains backward compatibility with existing DOM classes
+
+## [1.0.15] - Previous Release
+
+- Initial release with core DOM classes (DOMText, DOMButton, DOMInput, DOMLink, DOMNested, DOMMisc)
+- Plugin framework with FDO_SDK base class
+- IPC communication system
+- Storage backends (StoreDefault, StoreJson)
+- Logging and system integration utilities
diff --git a/README.md b/README.md
index f10e8e2..7e92331 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,102 @@
# SDK for FlexDevOPs Application modules
-Information about how to test example plugin is at examples/example_plugin.ts
+## Overview
+
+The FDO SDK provides a comprehensive toolkit for building application modules (plugins) for the FlexDevOps (FDO) desktop application ecosystem. This SDK enables developers to create modular extensions with rich UI capabilities using server-side HTML generation.
+
+## Features
+
+### DOM Element Generation
+
+The SDK provides extensive DOM element creation capabilities through specialized classes:
+
+- **DOMTable**: Create HTML tables with thead, tbody, tfoot, tr, th, td, and caption elements
+- **DOMMedia**: Create media elements including images with accessibility support
+- **DOMSemantic**: Create semantic HTML5 elements (article, section, nav, header, footer, aside, main)
+- **DOMNested**: Create container elements including ordered lists (ol), definition lists (dl, dt, dd), and more
+- **DOMInput**: Create form inputs including select dropdowns with options and optgroups
+- **DOMText**: Create text elements (headings, paragraphs, spans, etc.)
+- **DOMButton**: Create button elements with event handlers
+- **DOMLink**: Create anchor elements
+- **DOMMisc**: Create miscellaneous elements like horizontal rules
+
+All DOM classes support:
+- Custom CSS styling via goober CSS-in-JS
+- Custom classes and inline styles
+- Custom HTML attributes
+- Event handlers
+- Accessibility attributes
+
+### Plugin Framework
+
+- **FDO_SDK Base Class**: Abstract base class with lifecycle hooks (init, render)
+- **IPC Communication**: Message-based communication between plugin workers and main application
+- **Data Persistence**: Multiple storage backends (in-memory, JSON file-based)
+- **System Integration**: Logging, file operations, and privilege elevation
+
+## Getting Started
+
+### Installation
+
+```bash
+npm install @anikitenko/fdo-sdk
+```
+
+### Creating a Plugin
+
+```typescript
+import { FDO_SDK, FDOInterface, PluginMetadata } from "@anikitenko/fdo-sdk";
+
+export default class MyPlugin extends FDO_SDK implements FDOInterface {
+ private readonly _metadata: PluginMetadata = {
+ name: "My Plugin",
+ version: "1.0.0",
+ author: "Your Name",
+ description: "Plugin description",
+ icon: "icon.png"
+ };
+
+ get metadata(): PluginMetadata {
+ return this._metadata;
+ }
+
+ init(): void {
+ this.log("MyPlugin initialized!");
+ }
+
+ render(): string {
+ return "
Hello World
";
+ }
+}
+```
+
+### Example Usage
+
+See `examples/example_plugin.ts` for a basic plugin example.
+
+See `examples/dom_elements_plugin.ts` for comprehensive examples of using the new DOM element creation capabilities including tables, media, semantic HTML, lists, and form controls.
+
+## Development
+
+### Building
+
+```bash
+npm run build # Build webpack bundle
+npm run build:types # Generate TypeScript declarations
+```
+
+### Testing
+
+```bash
+npm test # Run Jest tests
+```
+
+## Documentation
+
+- Full API documentation is available in the TypeScript declaration files
+- All public methods include JSDoc comments with usage examples
+- See the `examples/` directory for working plugin implementations
+
+## License
+
+ISC
diff --git a/examples/dom_elements_plugin.ts b/examples/dom_elements_plugin.ts
new file mode 100644
index 0000000..30ee8e2
--- /dev/null
+++ b/examples/dom_elements_plugin.ts
@@ -0,0 +1,125 @@
+import { FDO_SDK, FDOInterface, PluginMetadata, DOMTable, DOMMedia, DOMSemantic, DOMNested, DOMInput } from "../src";
+
+/**
+ * Example plugin demonstrating the new DOM element creation capabilities.
+ * This plugin showcases tables, media elements, semantic HTML5 structures,
+ * ordered/definition lists, and select dropdowns.
+ */
+export default class DOMElementsExamplePlugin extends FDO_SDK implements FDOInterface {
+ private readonly _metadata: PluginMetadata = {
+ name: "DOM Elements Example",
+ version: "1.0.0",
+ author: "FDO SDK Team",
+ description: "Example plugin demonstrating new DOM element creation capabilities",
+ icon: "dom-icon.png"
+ };
+
+ get metadata(): PluginMetadata {
+ return this._metadata;
+ }
+
+ init(): void {
+ this.log("DOM Elements Example Plugin initialized!");
+ }
+
+ render(): string {
+ const domTable = new DOMTable();
+ const domMedia = new DOMMedia();
+ const domSemantic = new DOMSemantic();
+ const domNested = new DOMNested();
+ const domInput = new DOMInput("example-select", {});
+
+ const tableHeader1 = domTable.createTableHeader(["Name"], {}, undefined, { scope: "col" });
+ const tableHeader2 = domTable.createTableHeader(["Age"], {}, undefined, { scope: "col" });
+ const tableHeader3 = domTable.createTableHeader(["Role"], {}, undefined, { scope: "col" });
+ const headerRow = domTable.createTableRow([tableHeader1, tableHeader2, tableHeader3]);
+ const thead = domTable.createTableHead([headerRow]);
+
+ const cell1 = domTable.createTableCell(["John Doe"]);
+ const cell2 = domTable.createTableCell(["30"]);
+ const cell3 = domTable.createTableCell(["Developer"]);
+ const dataRow1 = domTable.createTableRow([cell1, cell2, cell3]);
+
+ const cell4 = domTable.createTableCell(["Jane Smith"]);
+ const cell5 = domTable.createTableCell(["28"]);
+ const cell6 = domTable.createTableCell(["Designer"]);
+ const dataRow2 = domTable.createTableRow([cell4, cell5, cell6]);
+
+ const tbody = domTable.createTableBody([dataRow1, dataRow2]);
+ const caption = domTable.createCaption(["Employee Directory"]);
+ const table = domTable.createTable([caption, thead, tbody], { classes: ["employee-table"] });
+
+ const image = domMedia.createImage(
+ "/assets/logo.png",
+ "Company Logo",
+ { classes: ["logo-image"] },
+ undefined,
+ { width: "200", height: "100", loading: "lazy" }
+ );
+
+ const header = domSemantic.createHeader(["Welcome to FDO SDK "]);
+ const nav = domSemantic.createNav([
+ "Home ",
+ "Documentation ",
+ "Examples "
+ ]);
+ const article = domSemantic.createArticle([
+ "New DOM Elements ",
+ "This plugin demonstrates the new DOM element creation capabilities.
"
+ ]);
+ const aside = domSemantic.createAside(["Quick Links "]);
+ const footer = domSemantic.createFooter(["© 2025 FDO SDK
"]);
+
+ const listItem1 = domNested.createListItem(["Install the SDK"]);
+ const listItem2 = domNested.createListItem(["Create your plugin"]);
+ const listItem3 = domNested.createListItem(["Build and test"]);
+ const orderedList = domNested.createOrderedList([listItem1, listItem2, listItem3], { classes: ["steps-list"] });
+
+ const term1 = domNested.createDefinitionTerm(["FDO"]);
+ const desc1 = domNested.createDefinitionDescription(["FlexDevOps - A desktop application framework"]);
+ const term2 = domNested.createDefinitionTerm(["SDK"]);
+ const desc2 = domNested.createDefinitionDescription(["Software Development Kit"]);
+ const definitionList = domNested.createDefinitionList([term1, desc1, term2, desc2], { classes: ["glossary"] });
+
+ const option1 = new DOMInput("", {}).createOption("Select an option", "", true);
+ const option2 = new DOMInput("", {}).createOption("Option A", "a");
+ const option3 = new DOMInput("", {}).createOption("Option B", "b");
+ const option4 = new DOMInput("", {}).createOption("Option C", "c");
+ const select = domInput.createSelect([option1, option2, option3, option4], () => {
+ console.log("Selection changed");
+ });
+
+ const groupOpt1 = new DOMInput("", {}).createOption("Item 1", "1");
+ const groupOpt2 = new DOMInput("", {}).createOption("Item 2", "2");
+ const optgroup1 = new DOMInput("", {}).createOptgroup("Group 1", [groupOpt1, groupOpt2]);
+
+ const groupOpt3 = new DOMInput("", {}).createOption("Item 3", "3");
+ const groupOpt4 = new DOMInput("", {}).createOption("Item 4", "4");
+ const optgroup2 = new DOMInput("", {}).createOptgroup("Group 2", [groupOpt3, groupOpt4]);
+
+ const groupedSelect = new DOMInput("grouped-select", {}).createSelect([optgroup1, optgroup2]);
+
+ const mainContent = domSemantic.createMain([
+ header,
+ nav,
+ "Example 1: Data Table ",
+ table,
+ "Example 2: Image ",
+ image,
+ "Example 3: Semantic Structure ",
+ article,
+ aside,
+ "Example 4: Ordered List ",
+ orderedList,
+ "Example 5: Definition List ",
+ definitionList,
+ "Example 6: Select Dropdown ",
+ select,
+ "Example 7: Grouped Select ",
+ groupedSelect,
+ footer
+ ]);
+
+ return mainContent;
+ }
+}
diff --git a/jest.config.js b/jest.config.js
index 51721f1..1ed5159 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,6 +1,23 @@
export default {
preset: 'ts-jest',
testEnvironment: 'node',
+ // Collect coverage for source files and report in lcov + text formats
+ collectCoverage: true,
+ coverageDirectory: '/coverage',
+ coverageReporters: ['text', 'lcov'],
+ collectCoverageFrom: [
+ 'src/**/*.{ts,js}',
+ '!src/**/*.d.ts',
+ '!src/**/index.{ts,js}'
+ ],
+ coverageThreshold: {
+ global: {
+ branches: 80,
+ functions: 80,
+ lines: 80,
+ statements: 80
+ }
+ },
transform: {
"^.+\\.ts$": "ts-jest", // TS handled by ts-jest
"^.+\\.js$": "babel-jest", // JS (e.g., pify) handled by babel
diff --git a/package-lock.json b/package-lock.json
index 1389aa6..c2d6f82 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@anikitenko/fdo-sdk",
- "version": "1.0.15",
+ "version": "1.0.16",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@anikitenko/fdo-sdk",
- "version": "1.0.15",
+ "version": "1.0.16",
"license": "ISC",
"dependencies": {
"@expo/sudo-prompt": "github:expo/sudo-prompt",
diff --git a/package.json b/package.json
index 66469ca..962e957 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"type": "git",
"url": "https://github.com/anikitenko/fdo-sdk.git"
},
- "version": "1.0.15",
+ "version": "1.0.16",
"description": "SDK for FlexDevOPs (FDO) application modules",
"keywords": [
"fdo",
@@ -26,10 +26,12 @@
"files": [
"dist"
],
- "scripts": {
+ "scripts": {
"build": "webpack --config webpack.config.cjs",
"build:types": "tsc --emitDeclarationOnly",
- "test": "jest"
+ "test": "jest",
+ "test:coverage": "jest --coverage",
+ "coverage:open": "npx opn coverage/lcov-report/index.html || true"
},
"devDependencies": {
"@babel/core": "^7.26.10",
diff --git a/src/DOMInput.ts b/src/DOMInput.ts
index f199c0f..09d7f8a 100644
--- a/src/DOMInput.ts
+++ b/src/DOMInput.ts
@@ -46,4 +46,81 @@ export class DOMInput extends DOM {
return this.createElement("textarea", {...props, ...this.props});
}
-}
\ No newline at end of file
+
+ /**
+ * Creates a select dropdown element.
+ * @param children - The option or optgroup children of the select.
+ * @param onChange - Optional change event handler.
+ * @returns {string} - The rendered select element.
+ * @uiName Create select
+ * @example Create a select dropdown with options.
+ * const option1 = new DOMInput("", {}).createOption("Option 1", "value1");
+ * const option2 = new DOMInput("", {}).createOption("Option 2", "value2");
+ * const select = new DOMInput("my-select", {}).createSelect([option1, option2]);
+ */
+ public createSelect(children: any[], onChange?: Function): string {
+ const props = this.combineProperties("", this.options, this.id);
+ const selectProps = onChange ? {...props, onChange, ...this.props} : {...props, ...this.props};
+
+ const attributes = this.createAttributes(selectProps);
+ const onAttributes = onChange ? this.createOnAttributes(selectProps) : "";
+ const content = this.flattenChildren(children).join('');
+
+ let openTag;
+ if (attributes && onAttributes) {
+ openTag = ``;
+ } else if (attributes) {
+ openTag = ``;
+ } else if (onAttributes) {
+ openTag = ``;
+ } else {
+ openTag = ``;
+ }
+
+ return `${openTag}${content} `;
+ }
+
+ /**
+ * Creates an option element for use within a select.
+ * @param label - The visible text of the option.
+ * @param value - The value of the option.
+ * @param selected - Whether the option is selected by default.
+ * @param otherProps - Additional properties for the option.
+ * @returns {string} - The rendered option element.
+ * @uiName Create option
+ * @example Create an option element.
+ * const option = new DOMInput("", {}).createOption("Choose me", "value1", false);
+ */
+ public createOption(label: string, value: string, selected: boolean = false, otherProps?: Record): string {
+ const props = this.combineProperties("", this.options, this.id);
+ const optionProps = {...props, value, selected, ...otherProps};
+
+ const attributes = this.createAttributes(optionProps);
+ const openTag = attributes ? `` : ` `;
+
+ return `${openTag}${label} `;
+ }
+
+ /**
+ * Creates an optgroup element for grouping options within a select.
+ * @param label - The label for the option group.
+ * @param children - The option children of the optgroup.
+ * @param otherProps - Additional properties for the optgroup.
+ * @returns {string} - The rendered optgroup element.
+ * @uiName Create optgroup
+ * @example Create an optgroup with options.
+ * const option1 = new DOMInput("", {}).createOption("Sub 1", "val1");
+ * const option2 = new DOMInput("", {}).createOption("Sub 2", "val2");
+ * const optgroup = new DOMInput("", {}).createOptgroup("Group Label", [option1, option2]);
+ */
+ public createOptgroup(label: string, children: any[], otherProps?: Record): string {
+ const props = this.combineProperties("", this.options, this.id);
+ const optgroupProps = {...props, label, ...otherProps};
+
+ const attributes = this.createAttributes(optgroupProps);
+ const content = this.flattenChildren(children).join('');
+ const openTag = attributes ? `` : ``;
+
+ return `${openTag}${content} `;
+ }
+}
diff --git a/src/DOMMedia.ts b/src/DOMMedia.ts
new file mode 100644
index 0000000..8506867
--- /dev/null
+++ b/src/DOMMedia.ts
@@ -0,0 +1,41 @@
+import {DOM} from "./DOM";
+
+export class DOMMedia extends DOM {
+ /**
+ * Creates a new DOMMedia instance.
+ * @constructor - Creates a new DOMMedia instance for self-closing media elements.
+ */
+ constructor() {
+ super(true); // Media elements like img are self-closing
+ }
+
+ /**
+ * Creates an img element.
+ * @param src - The source URL of the image.
+ * @param alt - The alternative text for the image.
+ * @param options - The options to apply to the image.
+ * @param id - The id of the image.
+ * @param otherProps - Additional properties like width, height, loading.
+ * @returns {string} - The rendered img element.
+ * @uiName Create image
+ * @example Create an image with alt text and dimensions.
+ * const img = new DOMMedia().createImage("/path/to/image.png", "Description", {}, undefined, { width: "300", height: "200", loading: "lazy" });
+ */
+ public createImage(
+ src: string,
+ alt: string,
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string,
+ otherProps?: Record
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("img", {...props, src, alt, ...otherProps});
+ }
+}
diff --git a/src/DOMNested.ts b/src/DOMNested.ts
index 41059e9..9649f39 100644
--- a/src/DOMNested.ts
+++ b/src/DOMNested.ts
@@ -174,4 +174,116 @@ export class DOMNested extends DOM {
return this.createElement("form", props, children);
}
-}
\ No newline at end of file
+
+ /**
+ * Creates a new ordered list
+ * @param children - The children of the ordered list.
+ * @uiName Create ordered list
+ * @param options - The options to apply to the ordered list.
+ * @param id - The id of the ordered list.
+ * @returns {string} - The rendered ordered list.
+ * @example Create a new ordered list.
+ * const child1 = new DOMNested().createListItem(["First item"]);
+ * const child2 = new DOMNested().createListItem(["Second item"]);
+ * const ol = new DOMNested().createOrderedList([child1, child2]);
+ */
+ public createOrderedList(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id)
+
+ // Merge custom attributes (like data-static) into props
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value
+ }
+ }
+
+ return this.createElement("ol", props, children);
+ }
+
+ /**
+ * Creates a new definition list
+ * @param children - The children of the definition list (dt and dd elements).
+ * @uiName Create definition list
+ * @param options - The options to apply to the definition list.
+ * @param id - The id of the definition list.
+ * @returns {string} - The rendered definition list.
+ * @example Create a new definition list.
+ * const term1 = new DOMNested().createDefinitionTerm(["Term 1"]);
+ * const desc1 = new DOMNested().createDefinitionDescription(["Description 1"]);
+ * const dl = new DOMNested().createDefinitionList([term1, desc1]);
+ */
+ public createDefinitionList(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id)
+
+ // Merge custom attributes (like data-static) into props
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value
+ }
+ }
+
+ return this.createElement("dl", props, children);
+ }
+
+ /**
+ * Creates a new definition term
+ * @param children - The children of the definition term.
+ * @uiName Create definition term
+ * @param options - The options to apply to the definition term.
+ * @param id - The id of the definition term.
+ * @returns {string} - The rendered definition term.
+ * @example Create a new definition term.
+ * const dt = new DOMNested().createDefinitionTerm(["API"]);
+ */
+ public createDefinitionTerm(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id)
+
+ // Merge custom attributes (like data-static) into props
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value
+ }
+ }
+
+ return this.createElement("dt", props, children);
+ }
+
+ /**
+ * Creates a new definition description
+ * @param children - The children of the definition description.
+ * @uiName Create definition description
+ * @param options - The options to apply to the definition description.
+ * @param id - The id of the definition description.
+ * @returns {string} - The rendered definition description.
+ * @example Create a new definition description.
+ * const dd = new DOMNested().createDefinitionDescription(["Application Programming Interface"]);
+ */
+ public createDefinitionDescription(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id)
+
+ // Merge custom attributes (like data-static) into props
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value
+ }
+ }
+
+ return this.createElement("dd", props, children);
+ }
+}
diff --git a/src/DOMSemantic.ts b/src/DOMSemantic.ts
new file mode 100644
index 0000000..ee93919
--- /dev/null
+++ b/src/DOMSemantic.ts
@@ -0,0 +1,193 @@
+import {DOM} from "./DOM";
+
+export class DOMSemantic extends DOM {
+ /**
+ * Creates a new DOMSemantic instance.
+ * @constructor - Creates a new DOMSemantic instance.
+ */
+ constructor() {
+ super();
+ }
+
+ /**
+ * Creates an article element.
+ * @param children - The children of the article.
+ * @param options - The options to apply to the article.
+ * @param id - The id of the article.
+ * @returns {string} - The rendered article element.
+ * @uiName Create article
+ * @example Create an article section.
+ * const article = new DOMSemantic().createArticle(["Article Title Content...
"]);
+ */
+ public createArticle(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("article", props, children);
+ }
+
+ /**
+ * Creates a section element.
+ * @param children - The children of the section.
+ * @param options - The options to apply to the section.
+ * @param id - The id of the section.
+ * @returns {string} - The rendered section element.
+ * @uiName Create section
+ * @example Create a section.
+ * const section = new DOMSemantic().createSection(["Section Title Content...
"]);
+ */
+ public createSection(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("section", props, children);
+ }
+
+ /**
+ * Creates a nav element.
+ * @param children - The children of the nav (navigation links).
+ * @param options - The options to apply to the nav.
+ * @param id - The id of the nav.
+ * @returns {string} - The rendered nav element.
+ * @uiName Create nav
+ * @example Create a navigation menu.
+ * const nav = new DOMSemantic().createNav(["Home About "]);
+ */
+ public createNav(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("nav", props, children);
+ }
+
+ /**
+ * Creates a header element.
+ * @param children - The children of the header.
+ * @param options - The options to apply to the header.
+ * @param id - The id of the header.
+ * @returns {string} - The rendered header element.
+ * @uiName Create header
+ * @example Create a page header.
+ * const header = new DOMSemantic().createHeader(["Site Title ... "]);
+ */
+ public createHeader(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("header", props, children);
+ }
+
+ /**
+ * Creates a footer element.
+ * @param children - The children of the footer.
+ * @param options - The options to apply to the footer.
+ * @param id - The id of the footer.
+ * @returns {string} - The rendered footer element.
+ * @uiName Create footer
+ * @example Create a page footer.
+ * const footer = new DOMSemantic().createFooter(["© 2025 Company Name
"]);
+ */
+ public createFooter(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("footer", props, children);
+ }
+
+ /**
+ * Creates an aside element.
+ * @param children - The children of the aside.
+ * @param options - The options to apply to the aside.
+ * @param id - The id of the aside.
+ * @returns {string} - The rendered aside element.
+ * @uiName Create aside
+ * @example Create a sidebar.
+ * const aside = new DOMSemantic().createAside(["Related Links "]);
+ */
+ public createAside(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("aside", props, children);
+ }
+
+ /**
+ * Creates a main element.
+ * @param children - The children of the main content area.
+ * @param options - The options to apply to the main.
+ * @param id - The id of the main.
+ * @returns {string} - The rendered main element.
+ * @uiName Create main
+ * @example Create the main content area.
+ * const main = new DOMSemantic().createMain(["... "]);
+ */
+ public createMain(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("main", props, children);
+ }
+}
diff --git a/src/DOMTable.ts b/src/DOMTable.ts
new file mode 100644
index 0000000..35b5ed1
--- /dev/null
+++ b/src/DOMTable.ts
@@ -0,0 +1,231 @@
+import {DOM} from "./DOM";
+
+export class DOMTable extends DOM {
+ /**
+ * Creates a new DOMTable instance.
+ * @constructor - Creates a new DOMTable instance.
+ */
+ constructor() {
+ super();
+ }
+
+ /**
+ * Creates a table element.
+ * @param children - The children of the table (thead, tbody, tfoot, caption).
+ * @param options - The options to apply to the table.
+ * @param id - The id of the table.
+ * @returns {string} - The rendered table element.
+ * @uiName Create table
+ * @example Create a table with headers and rows.
+ * const thead = new DOMTable().createTableHead([...]);
+ * const tbody = new DOMTable().createTableBody([...]);
+ * const table = new DOMTable().createTable([thead, tbody]);
+ */
+ public createTable(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("table", props, children);
+ }
+
+ /**
+ * Creates a thead element.
+ * @param children - The children of the thead (typically tr elements with th).
+ * @param options - The options to apply to the thead.
+ * @param id - The id of the thead.
+ * @returns {string} - The rendered thead element.
+ * @uiName Create table head
+ * @example Create a table head with header row.
+ * const headerRow = new DOMTable().createTableRow([...]);
+ * const thead = new DOMTable().createTableHead([headerRow]);
+ */
+ public createTableHead(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("thead", props, children);
+ }
+
+ /**
+ * Creates a tbody element.
+ * @param children - The children of the tbody (typically tr elements with td).
+ * @param options - The options to apply to the tbody.
+ * @param id - The id of the tbody.
+ * @returns {string} - The rendered tbody element.
+ * @uiName Create table body
+ * @example Create a table body with data rows.
+ * const row1 = new DOMTable().createTableRow([...]);
+ * const row2 = new DOMTable().createTableRow([...]);
+ * const tbody = new DOMTable().createTableBody([row1, row2]);
+ */
+ public createTableBody(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("tbody", props, children);
+ }
+
+ /**
+ * Creates a tfoot element.
+ * @param children - The children of the tfoot (typically tr elements).
+ * @param options - The options to apply to the tfoot.
+ * @param id - The id of the tfoot.
+ * @returns {string} - The rendered tfoot element.
+ * @uiName Create table foot
+ * @example Create a table footer with summary row.
+ * const footerRow = new DOMTable().createTableRow([...]);
+ * const tfoot = new DOMTable().createTableFoot([footerRow]);
+ */
+ public createTableFoot(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("tfoot", props, children);
+ }
+
+ /**
+ * Creates a tr (table row) element.
+ * @param children - The children of the row (th or td elements).
+ * @param options - The options to apply to the row.
+ * @param id - The id of the row.
+ * @returns {string} - The rendered tr element.
+ * @uiName Create table row
+ * @example Create a table row with cells.
+ * const cell1 = new DOMTable().createTableCell(["Data 1"]);
+ * const cell2 = new DOMTable().createTableCell(["Data 2"]);
+ * const row = new DOMTable().createTableRow([cell1, cell2]);
+ */
+ public createTableRow(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("tr", props, children);
+ }
+
+ /**
+ * Creates a th (table header cell) element.
+ * @param children - The content of the header cell.
+ * @param options - The options to apply to the header cell.
+ * @param id - The id of the header cell.
+ * @param otherProps - Additional properties like scope, colspan, rowspan.
+ * @returns {string} - The rendered th element.
+ * @uiName Create table header
+ * @example Create a table header cell.
+ * const header = new DOMTable().createTableHeader(["Name"], {}, undefined, { scope: "col" });
+ */
+ public createTableHeader(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string,
+ otherProps?: Record
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("th", {...props, ...otherProps}, children);
+ }
+
+ /**
+ * Creates a td (table data cell) element.
+ * @param children - The content of the data cell.
+ * @param options - The options to apply to the data cell.
+ * @param id - The id of the data cell.
+ * @param otherProps - Additional properties like colspan, rowspan.
+ * @returns {string} - The rendered td element.
+ * @uiName Create table cell
+ * @example Create a table data cell.
+ * const cell = new DOMTable().createTableCell(["John Doe"]);
+ */
+ public createTableCell(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string,
+ otherProps?: Record
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("td", {...props, ...otherProps}, children);
+ }
+
+ /**
+ * Creates a caption element for a table.
+ * @param children - The content of the caption.
+ * @param options - The options to apply to the caption.
+ * @param id - The id of the caption.
+ * @returns {string} - The rendered caption element.
+ * @uiName Create caption
+ * @example Create a table caption.
+ * const caption = new DOMTable().createCaption(["Employee List"]);
+ */
+ public createCaption(
+ children: any[],
+ options: Partial }> = DOM.DEFAULT_OPTIONS,
+ id?: string
+ ): string {
+ const props = this.combineProperties("", options, id);
+
+ if (options.customAttributes) {
+ for (const [attr, value] of Object.entries(options.customAttributes)) {
+ props[attr] = value;
+ }
+ }
+
+ return this.createElement("caption", props, children);
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index ad72ca3..a63992d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -20,6 +20,9 @@ export * from "./DOMLink";
export * from "./DOMNested";
export * from "./DOMText";
export * from "./DOMMisc";
+export * from "./DOMTable";
+export * from "./DOMMedia";
+export * from "./DOMSemantic";
declare global {
interface Window {
diff --git a/tests/DOMInput.test.ts b/tests/DOMInput.test.ts
index 2e0268c..bfbc3e9 100644
--- a/tests/DOMInput.test.ts
+++ b/tests/DOMInput.test.ts
@@ -31,4 +31,319 @@ describe("DOMInput", () => {
const textarea = domInput.createTextarea();
expect(textarea).toBe(``)
})
-})
\ No newline at end of file
+
+ describe("style and class handling", () => {
+ it("should handle custom styles", () => {
+ const select = new DOMInput("test-id", {
+ style: {
+ color: 'red',
+ backgroundColor: 'blue'
+ }
+ }).createSelect([]);
+ expect(select).toMatch(/className="[^"]*go[^"]*"/);
+ });
+
+ it("should handle custom classes", () => {
+ const select = new DOMInput("test-id", {
+ classes: ['custom-class-1', 'custom-class-2']
+ }).createSelect([]);
+ expect(select).toContain('custom-class-1 custom-class-2');
+ });
+
+ it("should handle both custom styles and classes", () => {
+ const select = new DOMInput("test-id", {
+ style: { color: 'red' },
+ classes: ['custom-class']
+ }).createSelect([]);
+ expect(select).toContain('custom-class');
+ expect(select).toMatch(/className="[^"]*go[^"]*"/);
+ });
+
+ it("should handle disabled default class", () => {
+ // pass style: undefined to avoid the DEFAULT_OPTIONS empty style
+ // which otherwise causes createClassFromStyle to generate a class (e.g. go11)
+ const select = new DOMInput("test-id", {
+ disableDefaultClass: true,
+ classes: ['custom-class'],
+ style: undefined as any
+ }).createSelect([]);
+ expect(select).toContain('custom-class');
+ expect(select).not.toContain('go11');
+ });
+ });
+
+ describe("createSelect", () => {
+ describe("attribute combinations", () => {
+ it("should create a select with neither attributes nor onChange", () => {
+ // We can't really get a select with no attributes since DOM base class adds some,
+ // but we can verify that it doesn't have onChange
+ const select = new DOMInput("", {}, {}).createSelect([]);
+ expect(select).not.toContain('onChange=');
+ });
+
+ it("should create a select with only regular attributes", () => {
+ const select = new DOMInput("test-id", {}, { 'data-testid': 'test' }).createSelect([]);
+ expect(select).toContain('data-testid="test"');
+ expect(select).not.toContain('onChange=');
+ });
+
+ it("should create a select with only onChange handler", () => {
+ const mockOnChange = () => {};
+ const select = new DOMInput("", {}, {}).createSelect([], mockOnChange);
+ expect(select).toContain('onChange=');
+ });
+
+ it("should create a select with both regular attributes and onChange", () => {
+ const mockOnChange = () => {};
+ const select = new DOMInput("test-id", {}, { 'data-testid': 'test' })
+ .createSelect([], mockOnChange);
+ expect(select).toContain('data-testid="test"');
+ expect(select).toContain('onChange=');
+ });
+
+ it("should merge props correctly with onChange", () => {
+ const mockOnChange = () => {};
+ const select = new DOMInput("test-id", {}, {
+ required: true,
+ 'aria-label': 'Select option'
+ }).createSelect([], mockOnChange);
+ expect(select).toContain('required');
+ expect(select).toContain('aria-label="Select option"');
+ expect(select).toContain('onChange=');
+ });
+
+ it("should handle undefined props with onChange", () => {
+ const mockOnChange = () => {};
+ const select = new DOMInput("test-id").createSelect([], mockOnChange);
+ expect(select).toContain('onChange=');
+ });
+
+ it("should handle empty options object", () => {
+ const select = new DOMInput("test-id", {}, undefined).createSelect([]);
+ expect(select).toContain('id="test-id"');
+ });
+
+ it("should handle boolean attributes correctly", () => {
+ const select = new DOMInput("test-id", {}, {
+ required: true,
+ disabled: false,
+ multiple: true,
+ 'data-boolean': true, // custom boolean-like attribute
+ open: false
+ }).createSelect([]);
+
+ // Standard boolean attributes
+ expect(select).toContain('required'); // true -> attribute present without value
+ expect(select).toContain('multiple'); // true -> attribute present without value
+ expect(select).not.toContain('disabled'); // false -> attribute omitted
+ expect(select).not.toContain('open'); // false -> attribute omitted
+
+ // Custom boolean-like attribute
+ expect(select).toContain('data-boolean="true"'); // non-standard -> value included
+ });
+
+ it("should handle mixed attribute types", () => {
+ const mockOnChange = () => {};
+ const select = new DOMInput("test-id", {}, {
+ required: true,
+ 'aria-required': "true",
+ 'data-custom': true,
+ selected: false,
+ placeholder: "Choose an option",
+ }).createSelect([], mockOnChange);
+
+ // Standard boolean
+ expect(select).toContain('required');
+ expect(select).not.toContain('selected');
+
+ // String attributes
+ expect(select).toContain('aria-required="true"');
+ expect(select).toContain('placeholder="Choose an option"');
+
+ // Custom boolean as string
+ expect(select).toContain('data-custom="true"');
+
+ // Event handler
+ expect(select).toContain('onChange=');
+ });
+ });
+
+ it("should create a select element with options", () => {
+ const option1 = new DOMInput("", {}).createOption("Option 1", "val1");
+ const option2 = new DOMInput("", {}).createOption("Option 2", "val2");
+ const select = new DOMInput("my-select", {}).createSelect([option1, option2]);
+ expect(select).toContain("");
+ expect(select).toContain("Option 1");
+ expect(select).toContain("Option 2");
+ });
+
+ it("should accept onChange handler", () => {
+ const mockOnChange = () => {};
+ const select = new DOMInput("my-select", {}).createSelect([], mockOnChange);
+ expect(select).toContain("onChange");
+ });
+
+ it("should apply custom classes to select", () => {
+ const select = new DOMInput("my-select", { classes: ["custom-select"] }).createSelect([]);
+ expect(select).toContain("custom-select");
+ });
+
+ it("should create a select with custom props", () => {
+ const select = new DOMInput("my-select", {}, {
+ required: true,
+ 'data-testid': 'test-select'
+ }).createSelect([]);
+ expect(select).toContain('required');
+ expect(select).toContain('data-testid="test-select"');
+ });
+
+ it("should handle empty array children", () => {
+ const select = new DOMInput("my-select", {}).createSelect([]);
+ expect(select).toBe(' ');
+ });
+
+ it("should create select with onChange handler and no other attributes", () => {
+ const mockOnChange = () => {};
+ const select = new DOMInput("", {}, {}).createSelect([], mockOnChange);
+ expect(select).toContain('onChange');
+ });
+
+ it("should create select with both regular attributes and onChange handler", () => {
+ const mockOnChange = () => {};
+ const select = new DOMInput("test-select", {}, { 'data-testid': 'combined-select' })
+ .createSelect([], mockOnChange);
+ expect(select).toContain('data-testid="combined-select"');
+ expect(select).toContain('onChange');
+ });
+
+ it("should create select with neither regular nor event attributes", () => {
+ // Create a DOMInput instance with minimal props that won't generate attributes
+ const select = new DOMInput("", {}, {}).createSelect([]);
+ expect(select).toMatch(/]*><\/select>/);
+ });
+ });
+
+ describe("createOption", () => {
+ it("should create an option element", () => {
+ const option = new DOMInput("", {}).createOption("Label", "value1");
+ expect(option).toContain("");
+ expect(option).toContain('value="value1"');
+ expect(option).toContain("Label");
+ });
+
+ it("should mark option as selected", () => {
+ const option = new DOMInput("", {}).createOption("Label", "value1", true);
+ expect(option).toContain("selected");
+ });
+
+ it("should not mark option as selected by default", () => {
+ const option = new DOMInput("", {}).createOption("Label", "value1");
+ expect(option).not.toContain("selected");
+ });
+
+ it("should accept additional properties", () => {
+ const option = new DOMInput("", {}).createOption("Label", "value1", false, { disabled: true });
+ expect(option).toContain("disabled");
+ });
+
+ it("should create an option with minimal attributes", () => {
+ const option = new DOMInput("", {}).createOption("Plain Option", "plain");
+ expect(option).toMatch(/ ]*value="plain"[^>]*>Plain Option<\/option>/);
+ });
+
+ it("should handle option with custom class", () => {
+ const option = new DOMInput("test-id", {
+ classes: ["custom-option"]
+ }).createOption("Styled Option", "styled", false);
+ expect(option).toContain("custom-option");
+ });
+
+ it("should handle option with multiple custom properties", () => {
+ const option = new DOMInput("", {}).createOption(
+ "Custom Option",
+ "custom",
+ true,
+ {
+ disabled: true,
+ 'data-testid': 'test-option',
+ 'aria-label': 'Custom option'
+ }
+ );
+ expect(option).toContain('selected');
+ expect(option).toContain('disabled');
+ expect(option).toContain('data-testid="test-option"');
+ expect(option).toContain('aria-label="Custom option"');
+ });
+ });
+
+ describe("createOptgroup", () => {
+ it("should create an optgroup element", () => {
+ const option1 = new DOMInput("", {}).createOption("Sub 1", "val1");
+ const option2 = new DOMInput("", {}).createOption("Sub 2", "val2");
+ const optgroup = new DOMInput("", {}).createOptgroup("Group Label", [option1, option2]);
+ expect(optgroup).toContain(" ");
+ expect(optgroup).toContain('label="Group Label"');
+ expect(optgroup).toContain("Sub 1");
+ expect(optgroup).toContain("Sub 2");
+ });
+
+ it("should accept additional properties", () => {
+ const optgroup = new DOMInput("", {}).createOptgroup("Group", [], { disabled: true });
+ expect(optgroup).toContain("disabled");
+ });
+
+ it("should create an optgroup with minimal attributes", () => {
+ const optgroup = new DOMInput("", {}).createOptgroup("Group", []);
+ expect(optgroup).toMatch(/]*label="Group"[^>]*><\/optgroup>/);
+ });
+
+ it("should handle empty children array", () => {
+ const optgroup = new DOMInput("test-id", {}).createOptgroup("Empty Group", []);
+ expect(optgroup).toBe(' ');
+ });
+
+ it("should handle optgroup with custom class", () => {
+ const optgroup = new DOMInput("test-id", {
+ classes: ["custom-group"]
+ }).createOptgroup("Styled Group", []);
+ expect(optgroup).toContain("custom-group");
+ });
+ });
+
+ describe("integration - select with options", () => {
+ it("should create a complete select dropdown", () => {
+ const option1 = new DOMInput("", {}).createOption("Choose One", "", true);
+ const option2 = new DOMInput("", {}).createOption("Option A", "a");
+ const option3 = new DOMInput("", {}).createOption("Option B", "b");
+ const select = new DOMInput("dropdown", {}).createSelect([option1, option2, option3]);
+
+ expect(select).toContain(" {
+ const opt1 = new DOMInput("", {}).createOption("Item 1", "1");
+ const opt2 = new DOMInput("", {}).createOption("Item 2", "2");
+ const group1 = new DOMInput("", {}).createOptgroup("Group 1", [opt1, opt2]);
+
+ const opt3 = new DOMInput("", {}).createOption("Item 3", "3");
+ const opt4 = new DOMInput("", {}).createOption("Item 4", "4");
+ const group2 = new DOMInput("", {}).createOptgroup("Group 2", [opt3, opt4]);
+
+ const select = new DOMInput("grouped-select", {}).createSelect([group1, group2]);
+
+ expect(select).toContain(" {
+ let domMedia: DOMMedia;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ domMedia = new DOMMedia();
+ });
+
+ it("should be defined", () => {
+ expect(domMedia).toBeDefined();
+ });
+
+ describe("createImage", () => {
+ it("should create an img element with src and alt", () => {
+ const img = domMedia.createImage("/path/to/image.png", "Description");
+ expect(img).toContain(" ");
+ });
+
+ it("should apply custom classes and styles", () => {
+ const img = domMedia.createImage("/image.png", "Alt text", { classes: ["custom-img"] });
+ expect(img).toContain("custom-img");
+ });
+
+ it("should accept width and height attributes", () => {
+ const img = domMedia.createImage("/image.png", "Alt text", {}, undefined, { width: "300", height: "200" });
+ expect(img).toContain('width="300"');
+ expect(img).toContain('height="200"');
+ });
+
+ it("should accept loading attribute", () => {
+ const img = domMedia.createImage("/image.png", "Alt text", {}, undefined, { loading: "lazy" });
+ expect(img).toContain('loading="lazy"');
+ });
+
+ it("should be self-closing", () => {
+ const img = domMedia.createImage("/image.png", "Alt text");
+ expect(img).toContain(" />");
+ expect(img).not.toContain("");
+ });
+
+ it("should accept custom attributes", () => {
+ const img = domMedia.createImage("/image.png", "Alt text", { customAttributes: { "data-test": "value" } });
+ expect(img).toContain('data-test="value"');
+ });
+ });
+});
diff --git a/tests/DOMNested.test.ts b/tests/DOMNested.test.ts
index ee455cf..75f59d1 100644
--- a/tests/DOMNested.test.ts
+++ b/tests/DOMNested.test.ts
@@ -173,4 +173,102 @@ describe("DOMNested", () => {
expect(output).toContain(`aria-label="Test Div"`);
expect(output).toContain(`data-value="123"`);
});
+
+ describe("createOrderedList", () => {
+ it("should create an ol element", () => {
+ const child1 = domNested.createListItem(["First item"]);
+ const child2 = domNested.createListItem(["Second item"]);
+ const ol = domNested.createOrderedList([child1, child2]);
+ expect(ol).toContain("");
+ expect(ol).toContain("First item");
+ expect(ol).toContain("Second item");
+ });
+
+ it("should apply custom classes to ordered list", () => {
+ const ol = domNested.createOrderedList([], { classes: ["custom-ol"] });
+ expect(ol).toContain("custom-ol");
+ });
+
+ it("should accept custom attributes on ordered list", () => {
+ const ol = domNested.createOrderedList([], { customAttributes: { "data-test": "value" } });
+ expect(ol).toContain('data-test="value"');
+ });
+ });
+
+ describe("createDefinitionList", () => {
+ it("should create a dl element", () => {
+ const term = domNested.createDefinitionTerm(["API"]);
+ const desc = domNested.createDefinitionDescription(["Application Programming Interface"]);
+ const dl = domNested.createDefinitionList([term, desc]);
+ expect(dl).toContain("");
+ expect(dl).toContain("API");
+ expect(dl).toContain("Application Programming Interface");
+ });
+
+ it("should apply custom classes to definition list", () => {
+ const dl = domNested.createDefinitionList([], { classes: ["custom-dl"] });
+ expect(dl).toContain("custom-dl");
+ });
+ });
+
+ describe("createDefinitionTerm", () => {
+ it("should create a dt element", () => {
+ const dt = domNested.createDefinitionTerm(["Term"]);
+ expect(dt).toContain("");
+ expect(dt).toContain("Term");
+ });
+
+ it("should apply custom classes to definition term", () => {
+ const dt = domNested.createDefinitionTerm(["Term"], { classes: ["custom-dt"] });
+ expect(dt).toContain("custom-dt");
+ });
+ });
+
+ describe("createDefinitionDescription", () => {
+ it("should create a dd element", () => {
+ const dd = domNested.createDefinitionDescription(["Description"]);
+ expect(dd).toContain(" ");
+ expect(dd).toContain("Description");
+ });
+
+ it("should apply custom classes to definition description", () => {
+ const dd = domNested.createDefinitionDescription(["Description"], { classes: ["custom-dd"] });
+ expect(dd).toContain("custom-dd");
+ });
+ });
+
+ describe("integration - new list types", () => {
+ it("should create a complete ordered list structure", () => {
+ const item1 = domNested.createListItem(["Step 1"]);
+ const item2 = domNested.createListItem(["Step 2"]);
+ const item3 = domNested.createListItem(["Step 3"]);
+ const ol = domNested.createOrderedList([item1, item2, item3]);
+
+ expect(ol).toContain(" {
+ const term1 = domNested.createDefinitionTerm(["HTML"]);
+ const desc1 = domNested.createDefinitionDescription(["HyperText Markup Language"]);
+ const term2 = domNested.createDefinitionTerm(["CSS"]);
+ const desc2 = domNested.createDefinitionDescription(["Cascading Style Sheets"]);
+ const dl = domNested.createDefinitionList([term1, desc1, term2, desc2]);
+
+ expect(dl).toContain(" {
+ let dom: DOMSemantic;
+
+ beforeEach(() => {
+ dom = new DOMSemantic();
+ });
+
+ it("createClassFromStyle returns a class name", () => {
+ const cls = dom.createClassFromStyle({ margin: '0', color: 'blue' });
+ expect(typeof cls).toBe('string');
+ expect(cls.length).toBeGreaterThan(0);
+ });
+
+ it("createStyleKeyframe returns a keyframe class", () => {
+ const kf = dom.createStyleKeyframe(`from{opacity:0} to{opacity:1}`);
+ expect(typeof kf).toBe('string');
+ expect(kf.length).toBeGreaterThan(0);
+ });
+
+ it("renderHTML injects style and script placeholders", () => {
+ const out = dom.renderHTML('Hi
');
+ expect(out).toContain('plugin-script-placeholder');
+ expect(out).toMatch(/