als-layout 7.0.0 → 7.1.0

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/readme.md CHANGED
@@ -1,163 +1,186 @@
1
- # als-layout Documentation
2
-
3
- `als-layout` is a JavaScript library designed to simplify and enhance the process of constructing and managing web page layouts. It provides a comprehensive API for modifying HTML documents dynamically, allowing developers to add, update, and manipulate various elements such as meta tags, styles, scripts, and more.
4
-
5
- The `als-layout` library is versatile, suitable for:
6
- - Building dynamic web pages that require frequent updates to their metadata, styles, or scripts.
7
- - Creating templating systems where multiple page layouts share similar structures but differ in content or styling.
8
- - Developing web applications that need to dynamically adjust their UI based on user interactions or data changes.
9
-
10
- ---
11
-
12
- ## Installation and Import
13
-
14
- Install via npm:
15
-
16
- ```bash
17
- npm i als-layout
18
- ```
19
-
20
- ### Node (CommonJS)
21
- ```js
22
- const Layout = require('als-layout')
23
- ```
24
-
25
- ### ESM (Module)
26
- ```js
27
- import Layout from 'als-layout'
28
- ```
29
-
30
- ### Browser (with bundle)
31
- ```html
32
- <script src="layout.js"></script>
33
- <script>
34
- const layout = new Layout()
35
- </script>
36
- ```
37
-
38
- ---
39
-
40
- ## Change Log for V7
41
-
42
- **⚠ Breaking changes: not backward compatible.**
43
-
44
- ### Removed
45
- - `uglify js and css`
46
- - `status` method
47
- - `end` method
48
- - `minified` attribute
49
- - `propsToClone`
50
-
51
- ### Changed
52
- - All code is merged into a single file
53
- - Added **ESM module build** (`index.mjs`)
54
- - Added **browser bundle** (`layout.js`) that includes `als-document` and exposes a global `Layout`
55
-
56
- ---
57
-
58
- ## Basic Usage
59
-
60
- ### Initialization
61
- ```js
62
- const layout = new Layout();
63
- ```
64
-
65
- ### Adding Elements
66
- ```js
67
- const layout = new Layout()
68
- .viewport() // default: width=device-width, initial-scale=1.0
69
- .title('Test title') // sets <title> and meta[og:title]
70
- .favicon('/favicon.png') // adds/updates favicon link
71
- .keywords(['some', 'keyword']) // updates meta[name=keywords]
72
- .image('/main-image.png') // sets og:image, twitter:image, twitter:card
73
- .description('Cool site') // sets meta description tags
74
- .url('/some', 'http://site.com') // sets canonical + og:url
75
- .style('body {margin:0; background-color:whitesmoke;}') // appends CSS to style tag
76
- .link('/styles.css') // adds stylesheet link if not already present
77
- .script({src:'/app.js'}) // external script
78
- .script({}, 'console.log("hello world")', false) // inline script in body
79
-
80
- // Accessors
81
- layout.body // getter for <body>
82
- layout.head // getter for <head>
83
- layout.html // getter for <html>
84
-
85
- // Outputs
86
- layout.rawHtml // raw HTML string
87
- layout.clone // cloned Layout instance
88
- ```
89
-
90
- ---
91
-
92
- ## Cloning
93
-
94
- ### Why Clone?
95
- Cloning creates a complete, independent copy of a `Layout` instance. Useful when generating multiple pages from a base template.
96
-
97
- ### Example
98
- ```js
99
- const newLayout = layout.clone;
100
- newLayout.title('Cloned page');
101
- ```
102
-
103
- - **Efficiency** – avoids rebuilding from scratch
104
- - **Isolation** – modifications to clones don’t affect the original
105
- - **Performance** – fast even for larger layouts
106
-
107
- ---
108
-
109
- ## Advanced Usage
110
-
111
- ```js
112
- const raw = /*html*/`<html></html>`
113
- const host = 'http://example.com';
114
-
115
- const layout = new Layout(raw, host).lang('fr');
116
- console.log(layout.rawHtml);
117
- // <!DOCTYPE html><html lang="fr"><head></head><body></body></html>
118
-
119
- const homePage = layout.clone;
120
- homePage.title('Home page');
121
- homePage.body.innerHTML = `<h1>Home page</h1>`;
122
- console.log(homePage.rawHtml);
123
-
124
- const autoReload = layout.clone;
125
- autoReload.script({}, 'setTimeout(() => window.location.reload(), 60000);', false);
126
- ```
127
-
128
- ---
129
-
130
- ## API
131
-
132
- ### Constructor
133
- ```js
134
- new Layout(html?: string, host?: string)
135
- ```
136
- - **html**: optional HTML string
137
- - **host**: base host for resolving relative URLs
138
-
139
- ### Properties
140
- - `layout.rawHtml` – returns the current document as HTML string
141
- - `layout.clone` returns a cloned instance
142
-
143
- ### Methods
144
- - `lang(lang: string)` – sets `<html lang>`
145
- - `title(title: string)` sets document `<title>` and Open Graph title
146
- - `description(description: string)` sets description meta tags
147
- - `favicon(href: string)` sets or updates favicon
148
- - `meta(props: object)` sets or updates a `<meta>` tag
149
- - `keywords(keywords: string[])` – sets or appends to `meta[name=keywords]`
150
- - `viewport(viewport: string = 'width=device-width, initial-scale=1.0')` – sets viewport meta
151
- - `image(image: string)` sets OG and Twitter image meta tags
152
- - `style(styles: string)` appends CSS styles into `<style>`
153
- - `url(url: string, host?: string)` sets canonical + og:url
154
- - `script(attrs: object = {}, innerHTML: string = '', head: boolean = true)` adds `<script>` tag
155
- - `link(href: string, attributes: object = { rel: "stylesheet", type: "text/css" })` – adds stylesheet link
156
-
157
- ---
158
-
159
- ## Notes
160
- - Version 7 removed **status**, **end**, **minified**, and **propsToClone**.
161
- - Use `clone` for creating independent layouts instead of extending clone properties.
162
- - Use `layout.js` in the browser for quick setup (exposes global `Layout`).
163
-
1
+ # als-layout
2
+
3
+ `als-layout` is a small, dependency‑light library for building and manipulating an HTML
4
+ document in memory — title, meta tags, Open Graph / Twitter cards, styles, scripts, links,
5
+ favicon and viewport with a fluent, chainable API. It works the same in Node (SSR /
6
+ static generation) and in the browser.
7
+
8
+ It is built on top of [`als-document`](https://www.npmjs.com/package/als-document) and is a
9
+ pure **document** builder: it has no global state and no site‑level concerns (collections,
10
+ sitemaps, robots). For multi‑page sites built on top of it, see
11
+ [`als-site`](https://www.npmjs.com/package/als-site).
12
+
13
+ ---
14
+
15
+ ## What's new in 7.1
16
+
17
+ - **`toDocument()`** — render a layout into the live DOM on the client (no‑op in Node).
18
+ - **`urlObj`** — `url()` now exposes the parsed `URL`.
19
+ - **Packaging** — ESM resolves to the named `Layout` export, CommonJS to the class (`exports` map).
20
+ - **Fix** — `twitter:description` uses `name=` (was `property=`), per the Twitter Cards spec.
21
+ - **Fix** — `clone` preserves the document URL/host.
22
+ - **Change** `url()` keeps the canonical trailing slash (`URL.href`) and stores `urlObj`.
23
+
24
+ See the full [Changelog](#changelog) below.
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm i als-layout
32
+ ```
33
+
34
+ ### ESM (named export)
35
+ ```js
36
+ import { Layout } from 'als-layout'
37
+ ```
38
+
39
+ ### Node (CommonJS)
40
+ ```js
41
+ const Layout = require('als-layout')
42
+ ```
43
+
44
+ ### Browser bundle (global `Layout`, `als-document` inlined)
45
+ ```html
46
+ <script src="layout.js"></script>
47
+ <script>const layout = new Layout()</script>
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Quick start
53
+
54
+ ```js
55
+ import { Layout } from 'als-layout'
56
+
57
+ const layout = new Layout(undefined, 'https://example.com')
58
+ .lang('en')
59
+ .viewport() // width=device-width, initial-scale=1.0
60
+ .title('Home') // <title> + og:title
61
+ .description('A cool site') // description + og/twitter description
62
+ .keywords(['stats', 'sheets'])
63
+ .favicon('/favicon.png')
64
+ .image('/cover.png') // og:image, twitter:image, twitter:card
65
+ .link('/styles.css') // <link rel=stylesheet> (deduped)
66
+ .style('body{margin:0}') // appends to <style>
67
+ .script({ src: '/app.js' }) // <script> in <head>
68
+ .script({}, 'console.log("hi")', false) // inline <script> in <body>
69
+ .url('/') // canonical + og:url, resolved vs host
70
+
71
+ layout.body.innerHTML = '<h1>Home</h1>'
72
+ console.log(layout.rawHtml) // full HTML string
73
+ ```
74
+
75
+ ---
76
+
77
+ ## A Layout is an offline DOM
78
+
79
+ This is the core idea: a `Layout` extends an `als-document` document, so the instance is a
80
+ real, in‑memory **DOM tree** you can query and mutate almost exactly like the browser DOM —
81
+ no `jsdom`, no browser, works in plain Node. The chainable `title()/meta()/link()/…` helpers
82
+ are just convenience on top of it; for anything else you manipulate nodes directly.
83
+
84
+ ```js
85
+ const layout = new Layout(/*html*/`<html><head></head><body><main></main></body></html>`)
86
+
87
+ // Query like the DOM ($ / $$ are aliases for querySelector / querySelectorAll)
88
+ const main = layout.$('main') // === layout.querySelector('main')
89
+ const items = layout.$$('.item') // === querySelectorAll, returns an array
90
+
91
+ // Build and insert nodes
92
+ main.innerHTML = '<h1>Hello</h1>'
93
+ main.insert(2, '<p class="lead">Intro</p>') // append parsed HTML
94
+ main.insertAdjacentHTML('beforeend', '<hr>')
95
+
96
+ // Mutate elements with the familiar API
97
+ const h1 = layout.$('h1')
98
+ h1.setAttribute('id', 'top')
99
+ h1.classList.add('title')
100
+ h1.dataset.role = 'heading'
101
+ h1.textContent = 'Welcome'
102
+ console.log(h1.outerHTML) // <h1 id="top" class="title" data-role="heading">Welcome</h1>
103
+
104
+ // Traverse
105
+ h1.parent // parent node
106
+ h1.nextElementSibling
107
+ layout.body.children
108
+
109
+ // Remove
110
+ layout.$('hr').remove()
111
+ ```
112
+
113
+ Supported on nodes: `querySelector`/`$`, `querySelectorAll`/`$$`, `getElementById`,
114
+ `getAttribute`/`setAttribute`/`removeAttribute`, `classList`, `dataset`, `style`,
115
+ `innerHTML`/`outerHTML`/`textContent`, `append`/`insert`/`insertAdjacentHTML`/`remove`,
116
+ plus traversal (`parent`, `children`, `prev`/`next`, …). See
117
+ [`als-document`](https://www.npmjs.com/package/als-document) for the full node API.
118
+
119
+ On the client, `toDocument()` flushes this offline DOM into the live `document`.
120
+
121
+ ---
122
+
123
+ ## Cloning
124
+
125
+ `layout.clone` returns a deep, independent copy — ideal as a template for many pages.
126
+ Changes to a clone never affect the original.
127
+
128
+ ```js
129
+ const base = new Layout(template, 'https://example.com').link('/styles.css')
130
+ const about = base.clone.title('About').url('/about')
131
+ ```
132
+
133
+ ---
134
+
135
+ ## API
136
+
137
+ ### Constructor
138
+ ```js
139
+ new Layout(html?: string, host?: string)
140
+ ```
141
+ - **html** optional HTML string to start from.
142
+ - **host** — base URL used to resolve relative URLs in `url()`.
143
+
144
+ ### Properties
145
+ - `rawHtml` current document as an HTML string.
146
+ - `clone` a deep, independent copy.
147
+ - `urlObj` — the parsed `URL` of the last `url()` call (set after `url()`).
148
+ - `head` / `body` / `html` element accessors (from `als-document`).
149
+
150
+ ### Methods (all chainable, return `this`)
151
+ - `lang(lang)` sets `<html lang>`.
152
+ - `title(title)` sets `<title>` and `og:title`.
153
+ - `description(text)` sets `description`, `og:description`, `twitter:description`.
154
+ - `keywords(list)` sets/merges `meta[name=keywords]`.
155
+ - `meta(props)` sets/updates a `<meta>` tag (keyed by its first attribute).
156
+ - `image(src)` — sets `og:image`, `twitter:image`, `twitter:card`.
157
+ - `favicon(href)` — sets/updates the favicon link.
158
+ - `viewport(content?)` — sets the viewport meta.
159
+ - `link(href, attributes?)` — adds a stylesheet `<link>` (skipped if already present).
160
+ - `style(css)` appends CSS to a `<style>` element.
161
+ - `script(attrs?, innerHTML?, head = true)` adds a `<script>` (head or body; `src` deduped).
162
+ - `url(url, host = this.URL)` sets canonical + `og:url`, stores `urlObj`.
163
+ - `toDocument()` — in the browser, renders this layout into the live `document` (no‑op in Node); returns `this`.
164
+
165
+ ---
166
+
167
+ ## Changelog
168
+
169
+ ### 7.1.0
170
+ - **Added** `toDocument()` — render a layout into the live DOM on the client.
171
+ - **Added** `urlObj` — `url()` now exposes the parsed `URL`.
172
+ - **Added** `exports` map — ESM resolves to the named `Layout` export, CommonJS to the class.
173
+ - **Fixed** `twitter:description` now uses `name=` (was `property=`), per the Twitter Cards spec.
174
+ - **Fixed** `clone` preserves the document URL/host.
175
+ - **Changed** `url()` keeps the canonical trailing slash (`URL.href`), and stores `urlObj`.
176
+
177
+ ### 7.0.0 (breaking)
178
+ - Merged into a single source file; added ESM build (`index.mjs`) and browser bundle (`layout.js`).
179
+ - Removed `status`, `end`, `minified`, `propsToClone`, and JS/CSS uglification.
180
+
181
+ ---
182
+
183
+ ## Notes
184
+ - Pure document builder — no global state, no file I/O.
185
+ - `index.mjs` and the browser `layout.js` are generated from `src/layout.js` by `node build.js`.
186
+ - Use `clone` to derive page variants from a base template.
package/src/layout.js CHANGED
@@ -1,85 +1,93 @@
1
- const { Document, SingleNode, Node } = require('als-document');
2
- class Layout extends Document {
3
- constructor(html, host) { super(html, host); this.root = this.html; }
4
- get rawHtml() { return this.innerHTML }
5
- get clone() { return new this.constructor(new Document(this, this.URL), this.host) }
6
- lang(lang) { this.html.setAttribute('lang', lang); return this }
7
- link(href, attributes = { rel: "stylesheet", type: "text/css" }) {
8
- if (!href || typeof href !== 'string') throw new Error(`href attribute must be a string`)
9
- if (!this.root.querySelector(`link[rel=stylesheet][href^="${href}"]`))
10
- this.head.insert(2, new SingleNode('link', { href, ...attributes }))
11
- return this
12
- }
13
- keywords(keywords = []) {
14
- let el = this.root.$('meta[name=keywords]') || new SingleNode('meta', { name: 'keywords' })
15
- keywords = new Set([...(el.getAttribute('content') || '').split(','),...keywords.map(k => k.trim())].filter(Boolean))
16
- if (keywords.size) el.setAttribute('content', Array.from(keywords).join(','))
17
- if(keywords.size && !el.parent) this.head.insert(2, el)
18
- return this
19
- }
20
- style(styles) {
21
- if (typeof styles !== 'string') throw 'styles parameter should be string';
22
- let el = this.root.$('style') || this.head.insert(2, new Node('style'))
23
- el.innerHTML = el.innerHTML + '\n' + styles;
24
- return this
25
- }
26
- url(url, host = this.URL) {
27
- try {
28
- url = (host ? new URL(url, host) : new URL(url)).href.replace(/\/$/, '')
29
- this.meta({ property: 'og:url', content: url })
30
- const el = this.root.$('link[rel="canonical"]') || this.head.insert(2, new SingleNode('link', { rel: 'canonical', href: url }))
31
- el.setAttribute('href', url)
32
- } catch (error) { error.info = `url "${url}" with host "${host}" is not valid url`; throw error; }
33
- return this
34
- }
35
- meta(props) {
36
- const entries = Object.entries(props)
37
- const [name, value] = entries[0]
38
- const metaElement = this.root.querySelector(`meta[${name}="${value}"]`) || this.head.insert(2, new SingleNode('meta', props))
39
- entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name]))
40
- return this
41
- }
42
- script(attrs = {}, innerHTML = '', head = true) {
43
- if (typeof attrs !== 'object' || attrs === null || Array.isArray(attrs)) attrs = {}
44
- if (attrs.src && this.root.querySelector(`script[src="${attrs.src}"]`)) return this
45
- if (Object.keys(attrs).length || innerHTML) {
46
- const script = new Node('script', attrs)
47
- if (innerHTML) script.innerHTML = innerHTML
48
- if (head) this.head.insert(2, script)
49
- else this.html.insert(2, script)
50
- }
51
- return this
52
- }
53
- description(description) {
54
- this.meta({ name: 'description', content: description })
55
- this.meta({ property: 'og:description', content: description })
56
- this.meta({ property: 'twitter:description', content: description })
57
- return this
58
- }
59
- favicon(href) {
60
- const el = this.root.$('link[rel=icon][type=image/x-icon]')
61
- if (el) el.setAttribute('href', href)
62
- else this.head.insert(2, new SingleNode('link', { rel: 'icon', href, type: 'image/x-icon' }))
63
- return this
64
- }
65
- viewport(viewport = 'width=device-width, initial-scale=1.0') {
66
- const el = this.root.$('meta[name="viewport"]')
67
- if (el) el.setAttribute('content', viewport)
68
- else this.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport }))
69
- return this
70
- }
71
- image(image) {
72
- this.meta({ property: 'og:image', content: image })
73
- this.meta({ name: 'twitter:image', content: image })
74
- this.meta({ name: 'twitter:card', content: 'summary_large_image' })
75
- return this
76
- }
77
- title(title) {
78
- super.title // create title tag if not exists
79
- super.title = title
80
- this.meta({ property: 'og:title', content: title })
81
- return this
82
- }
83
- }
84
-
85
- module.exports = Layout
1
+ const { Document, SingleNode, Node } = require('als-document');
2
+ class Layout extends Document {
3
+ constructor(html, host) { super(html, host); this.root = this.html; }
4
+ get rawHtml() { return this.innerHTML }
5
+ get clone() { return new this.constructor(new Document(this, this.URL), this.URL) }
6
+ lang(lang) { this.html.setAttribute('lang', lang); return this }
7
+ link(href, attributes = { rel: "stylesheet", type: "text/css" }) {
8
+ if (!href || typeof href !== 'string') throw new Error(`href attribute must be a string`)
9
+ if (!this.root.querySelector(`link[rel=stylesheet][href^="${href}"]`))
10
+ this.head.insert(2, new SingleNode('link', { href, ...attributes }))
11
+ return this
12
+ }
13
+ keywords(keywords = []) {
14
+ let el = this.root.$('meta[name=keywords]') || new SingleNode('meta', { name: 'keywords' })
15
+ keywords = new Set([...(el.getAttribute('content') || '').split(','),...keywords.map(k => k.trim())].filter(Boolean))
16
+ if (keywords.size) el.setAttribute('content', Array.from(keywords).join(','))
17
+ if(keywords.size && !el.parent) this.head.insert(2, el)
18
+ return this
19
+ }
20
+ style(styles) {
21
+ if (typeof styles !== 'string') throw 'styles parameter should be string';
22
+ let el = this.root.$('style') || this.head.insert(2, new Node('style'))
23
+ el.innerHTML = el.innerHTML + '\n' + styles;
24
+ return this
25
+ }
26
+ url(url, host = this.URL) {
27
+ try {
28
+ this.urlObj = (host ? new URL(url, host) : new URL(url))
29
+ url = this.urlObj.href
30
+ this.meta({ property: 'og:url', content: url })
31
+ const el = this.root.$('link[rel="canonical"]') || this.head.insert(2, new SingleNode('link', { rel: 'canonical', href: url }))
32
+ el.setAttribute('href', url)
33
+ } catch (error) { error.info = `url "${url}" with host "${host}" is not valid url`; throw error; }
34
+ return this
35
+ }
36
+ meta(props) {
37
+ const entries = Object.entries(props)
38
+ const [name, value] = entries[0]
39
+ const metaElement = this.root.querySelector(`meta[${name}="${value}"]`) || this.head.insert(2, new SingleNode('meta', props))
40
+ entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name]))
41
+ return this
42
+ }
43
+ script(attrs = {}, innerHTML = '', head = true) {
44
+ if (typeof attrs !== 'object' || attrs === null || Array.isArray(attrs)) attrs = {}
45
+ if (attrs.src && this.root.querySelector(`script[src="${attrs.src}"]`)) return this
46
+ if (Object.keys(attrs).length || innerHTML) {
47
+ const script = new Node('script', attrs)
48
+ if (innerHTML) script.innerHTML = innerHTML
49
+ if (head) this.head.insert(2, script)
50
+ else this.html.insert(2, script)
51
+ }
52
+ return this
53
+ }
54
+ description(description) {
55
+ this.meta({ name: 'description', content: description })
56
+ this.meta({ property: 'og:description', content: description })
57
+ this.meta({ name: 'twitter:description', content: description })
58
+ return this
59
+ }
60
+ favicon(href) {
61
+ const el = this.root.$('link[rel=icon][type=image/x-icon]')
62
+ if (el) el.setAttribute('href', href)
63
+ else this.head.insert(2, new SingleNode('link', { rel: 'icon', href, type: 'image/x-icon' }))
64
+ return this
65
+ }
66
+ viewport(viewport = 'width=device-width, initial-scale=1.0') {
67
+ const el = this.root.$('meta[name="viewport"]')
68
+ if (el) el.setAttribute('content', viewport)
69
+ else this.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport }))
70
+ return this
71
+ }
72
+ image(image) {
73
+ this.meta({ property: 'og:image', content: image })
74
+ this.meta({ name: 'twitter:image', content: image })
75
+ this.meta({ name: 'twitter:card', content: 'summary_large_image' })
76
+ return this
77
+ }
78
+ title(title) {
79
+ super.title // create title tag if not exists
80
+ super.title = title
81
+ this.meta({ property: 'og:title', content: title })
82
+ return this
83
+ }
84
+ toDocument() {
85
+ if (typeof window === 'undefined' || typeof document === 'undefined') return this
86
+ document.querySelector('title')?.remove()
87
+ document.head.innerHTML = this.head.innerHTML
88
+ document.body.outerHTML = this.body.outerHTML
89
+ return this
90
+ }
91
+ }
92
+
93
+ module.exports = Layout
package/build.js DELETED
@@ -1,8 +0,0 @@
1
- const { writeFileSync, readFileSync } = require('fs')
2
- const Layout = require('./src/layout.js');
3
- const content = Layout.toString()
4
-
5
- writeFileSync('index.mjs',`import { Document, SingleNode, Node } from 'als-document';\n`+'export '+content,'utf8' )
6
- const document = readFileSync('./node_modules/als-document/document.js','utf8')
7
-
8
- writeFileSync('layout.js',[document,`const { Document, SingleNode, Node } = alsDocument`,content].join('\n'))
@@ -1,28 +0,0 @@
1
- const assert = require('assert');
2
- const { describe, it } = require('node:test')
3
- const Layout = require('../src/layout.js');
4
-
5
- describe('Layout Initialization', () => {
6
- it('should create an instance of Layout', () => {
7
- const layout = new Layout();
8
- assert(layout instanceof Layout, 'layout is not an instance of Layout');
9
- });
10
-
11
- it('should initialize default language as "en"', () => {
12
- const layout = new Layout();
13
- assert(layout.html.getAttribute('lang') === 'en', 'default language is not "en"');
14
- });
15
-
16
- it('should allow setting a custom language', () => {
17
- const customLang = 'fr';
18
- const layout = new Layout(undefined).lang(customLang);
19
- layout.lang(customLang)
20
- assert(layout.html.getAttribute('lang') === customLang, `language is not set to ${customLang}`);
21
- });
22
-
23
- it('should allow setting a custom host', () => {
24
- const customHost = 'http://localhost';
25
- const layout = new Layout(undefined,customHost);
26
- assert.strictEqual(layout.URL, customHost, `host is not set to ${customHost}`);
27
- });
28
- });
@@ -1,33 +0,0 @@
1
- const assert = require('assert');
2
- const { describe, it,beforeEach } = require('node:test')
3
- const Layout = require('../src/layout.js');
4
-
5
- describe('description and title', () => {
6
- let layout;
7
-
8
- beforeEach(() => layout = new Layout());
9
-
10
- it('should add description correctly', () => {
11
- const description = 'Test Description';
12
- layout.description(description);
13
- assert.strictEqual(layout.root.$('meta[name="description"]').getAttribute('content'), description, 'Description not set correctly');
14
- assert.strictEqual(layout.root.$('meta[property="og:description"]').getAttribute('content'), description, 'Description not set correctly');
15
- assert.strictEqual(layout.root.$('meta[property="twitter:description"]').getAttribute('content'), description, 'Description not set correctly');
16
- });
17
-
18
- it('should add title correctly', () => {
19
- const title = 'Test Title';
20
- layout.title(title);
21
- assert(layout.root.$('title').innerHTML === title, 'Title not set correctly');
22
- assert(layout.root.$('[property="og:title"]').getAttribute('content') === title, 'Title not set correctly');
23
- });
24
-
25
- it('should add title if no title tag', () => {
26
- layout.$('title').remove()
27
- const title = 'Test Title';
28
- layout.title(title);
29
- assert(layout.root.$('title').innerHTML === title, 'Title not set correctly');
30
- assert(layout.root.$('[property="og:title"]').getAttribute('content') === title, 'Title not set correctly');
31
- });
32
-
33
- });