als-layout 3.0.1 → 4.0.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/docs/0-change-log.md +8 -0
- package/docs/1-basic-usage.md +0 -1
- package/docs/4-render.md +22 -17
- package/index.js +163 -1
- package/package.json +6 -9
- package/readme.md +30 -18
- package/tests/constructor.test.js +2 -14
- package/tests/description.test.js +33 -0
- package/tests/favicon.test.js +36 -0
- package/tests/image.test.js +42 -0
- package/tests/{layout.test.js → integrative.test.js} +1 -2
- package/tests/keywords.test.js +47 -0
- package/tests/link.test.js +83 -0
- package/tests/render/counter/App.js +8 -0
- package/tests/render/counter/Counter.js +9 -0
- package/tests/render/counter/build.js +12 -0
- package/tests/render.test.js +8 -15
- package/tests/script.test.js +57 -0
- package/tests/style.test.js +41 -0
- package/tests/url.test.js +50 -0
- package/tests/viewport.test.js +41 -0
- package/lib/elements/add-meta.js +0 -11
- package/lib/elements/charset.js +0 -8
- package/lib/elements/description.js +0 -9
- package/lib/elements/favicon.js +0 -10
- package/lib/elements/image.js +0 -11
- package/lib/elements/index.js +0 -15
- package/lib/elements/keywords.js +0 -20
- package/lib/elements/link.js +0 -13
- package/lib/elements/script.js +0 -18
- package/lib/elements/style.js +0 -30
- package/lib/elements/title.js +0 -11
- package/lib/elements/url.js +0 -17
- package/lib/elements/viewport.js +0 -8
- package/lib/layout.js +0 -30
- package/lib/onload.js +0 -9
- package/lib/render.js +0 -12
- package/tests/counter/App.js +0 -6
- package/tests/counter/build.js +0 -14
- package/tests/counter/counter.js +0 -14
- package/tests/elements.test.js +0 -437
- /package/tests/render/{App.js → render/App.js} +0 -0
- /package/tests/render/{Some.js → render/Some.js} +0 -0
package/docs/0-change-log.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## Change Log
|
|
2
2
|
|
|
3
|
+
* V4.0.0
|
|
4
|
+
* All code rebuilded and refactored
|
|
5
|
+
* No als-simple-css for style
|
|
6
|
+
* No charset method
|
|
7
|
+
* minifying for style and inner scripts
|
|
8
|
+
* updated render version
|
|
9
|
+
* render as element's method instead layout's method
|
|
10
|
+
|
|
3
11
|
* V3.0.0
|
|
4
12
|
* render switched to als-render
|
|
5
13
|
* updated bug with meta tags after body
|
package/docs/1-basic-usage.md
CHANGED
|
@@ -13,7 +13,6 @@ Once you have your `Layout` instance, you can easily add or modify various eleme
|
|
|
13
13
|
|
|
14
14
|
```js
|
|
15
15
|
const layout = new Layout()
|
|
16
|
-
.charset() // default UTF-8
|
|
17
16
|
.viewport() // default width=device-width, initial-scale=1.0
|
|
18
17
|
.title('Test title') // adding/updating title and meta[og:title]
|
|
19
18
|
.favicon('/favicon.png') // adding/updating link[rel=icon][type=image/x-icon] with new href
|
package/docs/4-render.md
CHANGED
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
## Rendering
|
|
2
|
-
Since version
|
|
2
|
+
Since version 4.0 , `als-layout` using `als-render` for rendering.
|
|
3
|
+
Not for production use yet.
|
|
3
4
|
|
|
4
5
|
### Counter Example
|
|
5
6
|
To demonstrate dynamic interaction, consider a counter that can be increased or decreased through user input:
|
|
6
7
|
|
|
7
8
|
The App.js
|
|
8
9
|
```jsx
|
|
9
|
-
const Counter = require('./
|
|
10
|
+
const Counter = require('./Counter')
|
|
10
11
|
function App() {
|
|
11
12
|
context.count = 0
|
|
12
|
-
return (<
|
|
13
|
+
return (<div>
|
|
14
|
+
<Counter count={0} />
|
|
15
|
+
</div>)
|
|
13
16
|
}
|
|
14
17
|
module.exports = App
|
|
15
18
|
```
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
Counter.js
|
|
18
21
|
```jsx
|
|
19
|
-
function Counter() {
|
|
20
|
-
function change(m=1) {
|
|
21
|
-
context.count += 1*m
|
|
22
|
-
Counter.update()
|
|
23
|
-
}
|
|
24
|
-
|
|
22
|
+
function Counter({count}) {
|
|
25
23
|
return (<div>
|
|
26
|
-
<button onclick={() =>
|
|
27
|
-
<span
|
|
28
|
-
<button onclick={() =>
|
|
24
|
+
<button onclick={() => this.update({count:count+1})}>Increase</button>
|
|
25
|
+
<span>{count}</span>
|
|
26
|
+
<button onclick={() => this.update({count:count-1})}>Decrease</button>
|
|
29
27
|
</div>)
|
|
30
28
|
}
|
|
31
29
|
|
|
@@ -36,9 +34,16 @@ build.js
|
|
|
36
34
|
```js
|
|
37
35
|
const fs = require('fs')
|
|
38
36
|
const Layout = require('als-layout')
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
|
|
38
|
+
// Create and configure the layout
|
|
39
|
+
const host = 'http://somehost.com'
|
|
40
|
+
const minified = false
|
|
41
|
+
const layout = new Layout('',host,minified).title('Counter')
|
|
42
|
+
|
|
43
|
+
const data = {}, includeBundle = true;
|
|
44
|
+
layout.$('body').render('./App',data,includeBundle)
|
|
45
|
+
|
|
46
|
+
// Write the output to a file
|
|
47
|
+
fs.writeFileSync('counter.html', layout.rawHtml, 'utf-8')
|
|
43
48
|
```
|
|
44
49
|
|
package/index.js
CHANGED
|
@@ -1,3 +1,165 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { Document, SingleNode, Node } = require('als-document');
|
|
2
|
+
const UglifyJS = require("uglify-js");
|
|
3
|
+
const uglifycss = require('uglifycss');
|
|
4
|
+
|
|
5
|
+
const render = require('als-render');
|
|
6
|
+
const onloadScript = /*js*/`document.addEventListener('DOMContentLoaded', function() {
|
|
7
|
+
const elements = document.querySelectorAll('[onload]');
|
|
8
|
+
elements.forEach(element => {
|
|
9
|
+
const onloadCode = element.getAttribute('onload');
|
|
10
|
+
const func = Function('"use strict"; return function() { ' + onloadCode + ' }');
|
|
11
|
+
func().call(element);
|
|
12
|
+
element.removeAttribute('onload');
|
|
13
|
+
});
|
|
14
|
+
});`;
|
|
15
|
+
|
|
16
|
+
class Layout extends Document {
|
|
17
|
+
get rawHtml() { return this.innerHTML }
|
|
18
|
+
get clone() { return new Layout(new Document(this), this.URL, this.minified) }
|
|
19
|
+
lang(lang) { this.html.setAttribute('lang', lang); return this }
|
|
20
|
+
version(v) { this.v = v; return this; }
|
|
21
|
+
|
|
22
|
+
constructor(html, host, minified = false) {
|
|
23
|
+
super(html, host);
|
|
24
|
+
this.minified = minified
|
|
25
|
+
this.root = this.html
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onload() {
|
|
29
|
+
if (this.onloadAdded) return
|
|
30
|
+
this.script({}, onloadScript);
|
|
31
|
+
this.onloadAdded = true
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
title(title) {
|
|
36
|
+
super.title // create title tag if not exists
|
|
37
|
+
super.title = title
|
|
38
|
+
this.meta({ property: 'og:title', content: title })
|
|
39
|
+
return this
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
description(description) {
|
|
43
|
+
this.meta({ name: 'description', content: description })
|
|
44
|
+
this.meta({ property: 'og:description', content: description })
|
|
45
|
+
this.meta({ property: 'twitter:description', content: description })
|
|
46
|
+
return this
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
favicon(href) {
|
|
50
|
+
const faviconElement = this.root.$('link[rel=icon][type=image/x-icon]')
|
|
51
|
+
if (faviconElement) faviconElement.setAttribute('href', href)
|
|
52
|
+
else this.head.insert(2, new SingleNode('link', { rel: 'icon', href, type: 'image/x-icon' }))
|
|
53
|
+
return this
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
meta(props) {
|
|
57
|
+
const entries = Object.entries(props)
|
|
58
|
+
const [name, value] = entries[0]
|
|
59
|
+
const selector = `meta[${name}="${value}"]`
|
|
60
|
+
const metaElement = this.root.$(selector)
|
|
61
|
+
if (metaElement) entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name]))
|
|
62
|
+
else this.head.insert(2, new SingleNode('meta', props))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
keywords(keywords = []) {
|
|
66
|
+
let keywordsElement = this.root.$('meta[name=keywords]')
|
|
67
|
+
if (!keywordsElement) keywordsElement = new SingleNode('meta', { name: 'keywords' })
|
|
68
|
+
const content = keywordsElement.getAttribute('content')
|
|
69
|
+
const existingKeywords = content ? content.split(',') : []
|
|
70
|
+
keywords.forEach(keyword => {
|
|
71
|
+
keyword = keyword.trim()
|
|
72
|
+
if (!existingKeywords.includes(keyword)) existingKeywords.push(keyword)
|
|
73
|
+
});
|
|
74
|
+
if (existingKeywords.length) {
|
|
75
|
+
keywordsElement.setAttribute('content', existingKeywords.join())
|
|
76
|
+
this.head.insert(2, keywordsElement)
|
|
77
|
+
}
|
|
78
|
+
return this
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
viewport(viewport = 'width=device-width, initial-scale=1.0') {
|
|
82
|
+
const element = this.root.$('meta[name="viewport"]')
|
|
83
|
+
if (element) element.setAttribute('content', viewport)
|
|
84
|
+
else this.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport }))
|
|
85
|
+
return this
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
image(image, version = this.v) {
|
|
89
|
+
if (image && version) image += (image.includes('?') ? '&' : '?') + `v=${version}`
|
|
90
|
+
this.meta({ property: 'og:image', content: image })
|
|
91
|
+
this.meta({ name: 'twitter:image', content: image })
|
|
92
|
+
this.meta({ name: 'twitter:card', content: 'summary_large_image' })
|
|
93
|
+
return this
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
style(styles, minified = this.minified) {
|
|
97
|
+
if (typeof styles !== 'string') throw 'styles parameter should be string';
|
|
98
|
+
if (minified) styles = uglifycss.processString(styles);
|
|
99
|
+
let styleElement = this.root.$('style')
|
|
100
|
+
if (styleElement) styleElement.innerHTML = styleElement.innerHTML + '\n' + styles
|
|
101
|
+
else {
|
|
102
|
+
styleElement = new Node('style')
|
|
103
|
+
styleElement.innerHTML = styles
|
|
104
|
+
this.head.insert(2, styleElement)
|
|
105
|
+
}
|
|
106
|
+
return this
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
url(url, host = this.URL) {
|
|
110
|
+
try {
|
|
111
|
+
url = new URL(url, host).href.replace(/\/$/, '')
|
|
112
|
+
this.meta({ property: 'og:url', content: url })
|
|
113
|
+
const canonicalElement = this.root.$('link[rel="canonical"]')
|
|
114
|
+
if (canonicalElement) canonicalElement.setAttribute('href', url)
|
|
115
|
+
else this.head.insert(2, new SingleNode('link', { rel: 'canonical', href: url }))
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.log(`url ${url} with host ${host} is not valid url`)
|
|
118
|
+
}
|
|
119
|
+
return this
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
script(attrs = {}, innerHTML = '', head = true, version = this.v,minified = this.minified) {
|
|
123
|
+
if (typeof attrs !== 'object' || attrs === null || Array.isArray(attrs)) attrs = {}
|
|
124
|
+
if (attrs.src) {
|
|
125
|
+
const selector = `script[src="${attrs.src}"]`
|
|
126
|
+
if (attrs.src && this.root.$(selector)) return this
|
|
127
|
+
if (attrs.src && version) attrs.src += (attrs.src.includes('?') ? '&' : '?') + `v=${version}`
|
|
128
|
+
}
|
|
129
|
+
if (Object.keys(attrs).length || innerHTML) {
|
|
130
|
+
const script = new Node('script', attrs)
|
|
131
|
+
if (innerHTML) {
|
|
132
|
+
if (minified) innerHTML = UglifyJS.minify(innerHTML).code
|
|
133
|
+
script.innerHTML = innerHTML
|
|
134
|
+
}
|
|
135
|
+
if (head) this.head.insert(2, script)
|
|
136
|
+
else this.body.insert(3, script)
|
|
137
|
+
}
|
|
138
|
+
return this
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
link(href, version = this.v) {
|
|
142
|
+
if (!href || typeof href !== 'string') return this
|
|
143
|
+
if (href && version) href += (href.includes('?') ? '&' : '?') + `v=${version}`
|
|
144
|
+
const selector = `link[rel=stylesheet][href^="${href}"]`
|
|
145
|
+
let linkElement = this.root.$(selector)
|
|
146
|
+
if (linkElement) return
|
|
147
|
+
linkElement = new SingleNode('link', { rel: 'stylesheet', href })
|
|
148
|
+
this.head.insert(2, linkElement)
|
|
149
|
+
return this
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
$(selector) {
|
|
153
|
+
const element = super.$(selector)
|
|
154
|
+
if (!element) return null
|
|
155
|
+
element.render = (path, data, includeBundle = true, contextName = 'context') => {
|
|
156
|
+
const { rawHtml, bundle } = render(path, data, contextName, false)
|
|
157
|
+
element.innerHTML = rawHtml
|
|
158
|
+
if (includeBundle) this.script({}, bundle, false)
|
|
159
|
+
return this
|
|
160
|
+
}
|
|
161
|
+
return element
|
|
162
|
+
}
|
|
163
|
+
}
|
|
2
164
|
|
|
3
165
|
module.exports = Layout
|
package/package.json
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "als-layout",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Html layout constructor",
|
|
3
|
+
"version": "4.0.0",
|
|
5
4
|
"main": "index.js",
|
|
6
|
-
"directories": {
|
|
7
|
-
"lib": "lib"
|
|
8
|
-
},
|
|
9
5
|
"scripts": {
|
|
10
6
|
"test": "node --test --experimental-test-coverage",
|
|
11
7
|
"report": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info"
|
|
12
8
|
},
|
|
13
9
|
"keywords": [],
|
|
14
10
|
"author": "Alex Sorkin",
|
|
15
|
-
"license": "
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"description": "Html layout constructor",
|
|
16
13
|
"dependencies": {
|
|
17
|
-
"als-css-parser": "^0.5.0",
|
|
18
14
|
"als-document": "^1.4.0",
|
|
19
|
-
"als-render": "^0.
|
|
20
|
-
"
|
|
15
|
+
"als-render": "^0.4.0",
|
|
16
|
+
"uglify-js": "^3.19.2",
|
|
17
|
+
"uglifycss": "^0.0.29"
|
|
21
18
|
}
|
|
22
19
|
}
|
package/readme.md
CHANGED
|
@@ -20,6 +20,14 @@ const Layout = require('als-layout')
|
|
|
20
20
|
|
|
21
21
|
## Change Log
|
|
22
22
|
|
|
23
|
+
* V4.0.0
|
|
24
|
+
* All code rebuilded and refactored
|
|
25
|
+
* No als-simple-css for style
|
|
26
|
+
* No charset method
|
|
27
|
+
* minifying for style and inner scripts
|
|
28
|
+
* updated render version
|
|
29
|
+
* render as element's method instead layout's method
|
|
30
|
+
|
|
23
31
|
* V3.0.0
|
|
24
32
|
* render switched to als-render
|
|
25
33
|
* updated bug with meta tags after body
|
|
@@ -40,7 +48,6 @@ Once you have your `Layout` instance, you can easily add or modify various eleme
|
|
|
40
48
|
|
|
41
49
|
```js
|
|
42
50
|
const layout = new Layout()
|
|
43
|
-
.charset() // default UTF-8
|
|
44
51
|
.viewport() // default width=device-width, initial-scale=1.0
|
|
45
52
|
.title('Test title') // adding/updating title and meta[og:title]
|
|
46
53
|
.favicon('/favicon.png') // adding/updating link[rel=icon][type=image/x-icon] with new href
|
|
@@ -139,33 +146,31 @@ This advanced example illustrates how `als-layout` can be used to handle complex
|
|
|
139
146
|
|
|
140
147
|
|
|
141
148
|
## Rendering
|
|
142
|
-
Since version
|
|
149
|
+
Since version 4.0 , `als-layout` using `als-render` for rendering.
|
|
150
|
+
Not for production use yet.
|
|
143
151
|
|
|
144
152
|
### Counter Example
|
|
145
153
|
To demonstrate dynamic interaction, consider a counter that can be increased or decreased through user input:
|
|
146
154
|
|
|
147
155
|
The App.js
|
|
148
156
|
```jsx
|
|
149
|
-
const Counter = require('./
|
|
157
|
+
const Counter = require('./Counter')
|
|
150
158
|
function App() {
|
|
151
159
|
context.count = 0
|
|
152
|
-
return (<
|
|
160
|
+
return (<div>
|
|
161
|
+
<Counter count={0} />
|
|
162
|
+
</div>)
|
|
153
163
|
}
|
|
154
164
|
module.exports = App
|
|
155
165
|
```
|
|
156
166
|
|
|
157
|
-
|
|
167
|
+
Counter.js
|
|
158
168
|
```jsx
|
|
159
|
-
function Counter() {
|
|
160
|
-
function change(m=1) {
|
|
161
|
-
context.count += 1*m
|
|
162
|
-
Counter.update()
|
|
163
|
-
}
|
|
164
|
-
|
|
169
|
+
function Counter({count}) {
|
|
165
170
|
return (<div>
|
|
166
|
-
<button onclick={() =>
|
|
167
|
-
<span
|
|
168
|
-
<button onclick={() =>
|
|
171
|
+
<button onclick={() => this.update({count:count+1})}>Increase</button>
|
|
172
|
+
<span>{count}</span>
|
|
173
|
+
<button onclick={() => this.update({count:count-1})}>Decrease</button>
|
|
169
174
|
</div>)
|
|
170
175
|
}
|
|
171
176
|
|
|
@@ -176,9 +181,16 @@ build.js
|
|
|
176
181
|
```js
|
|
177
182
|
const fs = require('fs')
|
|
178
183
|
const Layout = require('als-layout')
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
|
|
185
|
+
// Create and configure the layout
|
|
186
|
+
const host = 'http://somehost.com'
|
|
187
|
+
const minified = false
|
|
188
|
+
const layout = new Layout('',host,minified).title('Counter')
|
|
189
|
+
|
|
190
|
+
const data = {}, includeBundle = true;
|
|
191
|
+
layout.$('body').render('./App',data,includeBundle)
|
|
192
|
+
|
|
193
|
+
// Write the output to a file
|
|
194
|
+
fs.writeFileSync('counter.html', layout.rawHtml, 'utf-8')
|
|
183
195
|
```
|
|
184
196
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { describe, it } = require('node:test')
|
|
3
|
-
const Layout = require('../
|
|
4
|
-
const { Root,cacheDoc } = require('als-document')
|
|
3
|
+
const Layout = require('../index');
|
|
5
4
|
|
|
6
5
|
describe('Layout Initialization', () => {
|
|
7
6
|
it('should create an instance of Layout', () => {
|
|
@@ -16,25 +15,14 @@ describe('Layout Initialization', () => {
|
|
|
16
15
|
|
|
17
16
|
it('should allow setting a custom language', () => {
|
|
18
17
|
const customLang = 'fr';
|
|
19
|
-
const layout = new Layout(undefined
|
|
18
|
+
const layout = new Layout(undefined).lang(customLang);
|
|
20
19
|
layout.lang(customLang)
|
|
21
20
|
assert(layout.html.getAttribute('lang') === customLang, `language is not set to ${customLang}`);
|
|
22
21
|
});
|
|
23
22
|
|
|
24
|
-
it('should initialize development mode as undefined or false', () => {
|
|
25
|
-
const layout = new Layout();
|
|
26
|
-
assert.strictEqual(layout.dev, undefined, 'dev is not undefined or false by default');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should initialize host as undefined', () => {
|
|
30
|
-
const layout = new Layout();
|
|
31
|
-
assert.strictEqual(layout.host, undefined, 'host is not undefined by default');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
23
|
it('should allow setting a custom host', () => {
|
|
35
24
|
const customHost = 'http://localhost';
|
|
36
25
|
const layout = new Layout(undefined,customHost);
|
|
37
|
-
console.log(layout.URL)
|
|
38
26
|
assert.strictEqual(layout.URL, customHost, `host is not set to ${customHost}`);
|
|
39
27
|
});
|
|
40
28
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it,beforeEach } = require('node:test')
|
|
3
|
+
const Layout = require('../index');
|
|
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
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it, beforeEach } = require('node:test')
|
|
3
|
+
const { SingleNode } = require('als-document')
|
|
4
|
+
const Layout = require('../index');
|
|
5
|
+
|
|
6
|
+
describe('Favicon tests', () => {
|
|
7
|
+
let layout;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
layout = new Layout();
|
|
11
|
+
// layout.head.insert(1, new SingleNode('title', {})); // Добавляем элемент title для проверки позиции
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should update favicon href if link[rel="icon"] already exists', () => {
|
|
15
|
+
const oldHref = 'old-favicon.ico';
|
|
16
|
+
layout.head.insert(2, new SingleNode('link', { rel: 'icon', href: oldHref, type: 'image/x-icon' }));
|
|
17
|
+
const newHref = 'new-favicon.ico';
|
|
18
|
+
layout.favicon(newHref);
|
|
19
|
+
assert.strictEqual(layout.root.$('link[rel="icon"]').getAttribute('href'), newHref, 'Favicon href should be updated');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should insert favicon at the end of head if not present', () => {
|
|
23
|
+
const faviconHref = 'favicon.ico';
|
|
24
|
+
layout.favicon(faviconHref);
|
|
25
|
+
const element = layout.head.childNodes[layout.head.childNodes.length - 1]
|
|
26
|
+
assert.strictEqual(element.tagName, 'LINK', 'Favicon link should be at second position');
|
|
27
|
+
assert.strictEqual(element.getAttribute('href'), faviconHref, 'Favicon href not set correctly');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should add favicon correctly', () => {
|
|
31
|
+
const faviconHref = 'favicon.ico';
|
|
32
|
+
layout.favicon(faviconHref);
|
|
33
|
+
assert.strictEqual(layout.root.$('link[rel="icon"]').getAttribute('href'), faviconHref, 'Favicon not set correctly');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it,beforeEach } = require('node:test')
|
|
3
|
+
const Layout = require('../index');
|
|
4
|
+
|
|
5
|
+
describe('Image tests', () => {
|
|
6
|
+
let layout;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
layout = new Layout();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should add twitter:card meta tag', () => {
|
|
13
|
+
const imageUrl = 'test-image.jpg';
|
|
14
|
+
layout.image(imageUrl);
|
|
15
|
+
assert.strictEqual(layout.root.$('meta[name="twitter:card"]').getAttribute('content'), 'summary_large_image', 'twitter:card not set correctly');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should add image correctly', () => {
|
|
19
|
+
const imageUrl = 'test-image.jpg';
|
|
20
|
+
layout.image(imageUrl);
|
|
21
|
+
assert.strictEqual(layout.root.$('meta[property="og:image"]').getAttribute('content'), imageUrl, 'Image not set correctly');
|
|
22
|
+
assert.strictEqual(layout.root.$('meta[name="twitter:image"]').getAttribute('content'), imageUrl, 'Image not set correctly');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should handle cases where image URL already has query parameters', () => {
|
|
26
|
+
const imageUrl = 'test-image.jpg?existing=param';
|
|
27
|
+
const version = '456';
|
|
28
|
+
layout.image(imageUrl, version);
|
|
29
|
+
const expectedUrl = imageUrl + '&v=' + version;
|
|
30
|
+
const img = layout.root.$('meta[property="og:image"]').getAttribute('content')
|
|
31
|
+
assert(img === expectedUrl, 'Versioned image URL with existing parameters not set correctly');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should add version parameter to image URL if version is provided', () => {
|
|
35
|
+
const imageUrl = 'test-image.jpg';
|
|
36
|
+
const version = '123';
|
|
37
|
+
layout.image(imageUrl, version);
|
|
38
|
+
const expectedUrl = imageUrl + '?v=' + version;
|
|
39
|
+
assert.strictEqual(layout.root.$('meta[property="og:image"]').getAttribute('content'), expectedUrl, 'Versioned image URL not set correctly in og:image');
|
|
40
|
+
assert.strictEqual(layout.root.$('meta[name="twitter:image"]').getAttribute('content'), expectedUrl, 'Versioned image URL not set correctly in twitter:image');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { describe, it, beforeEach } = require('node:test')
|
|
3
|
-
const Layout = require('../
|
|
4
|
-
const { buildFromCache } = require('als-document')
|
|
3
|
+
const Layout = require('../index');
|
|
5
4
|
|
|
6
5
|
describe('Layout Integrative tests', () => {
|
|
7
6
|
let layout;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it, beforeEach } = require('node:test')
|
|
3
|
+
const Layout = require('../index');
|
|
4
|
+
const { SingleNode } = require('als-document')
|
|
5
|
+
|
|
6
|
+
describe('Keywords tests', () => {
|
|
7
|
+
let layout;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
layout = new Layout();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should add new keywords to an existing meta tag', () => {
|
|
14
|
+
layout.head.insert(2, new SingleNode('meta', { name: 'keywords', content: 'initial' }));
|
|
15
|
+
const additionalKeywords = ['keyword1', 'keyword2'];
|
|
16
|
+
layout.keywords(additionalKeywords);
|
|
17
|
+
const expectedContent = 'initial,keyword1,keyword2';
|
|
18
|
+
assert.strictEqual(layout.root.$('meta[name="keywords"]').getAttribute('content'), expectedContent, 'Existing keywords not updated correctly');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should not add duplicate keywords', () => {
|
|
22
|
+
layout.head.insert(2, new SingleNode('meta', { name: 'keywords', content: 'keyword1,keyword2' }));
|
|
23
|
+
const additionalKeywords = ['keyword2', 'keyword3'];
|
|
24
|
+
layout.keywords(additionalKeywords);
|
|
25
|
+
const expectedContent = 'keyword1,keyword2,keyword3';
|
|
26
|
+
assert.strictEqual(layout.root.$('meta[name="keywords"]').getAttribute('content'), expectedContent, 'Duplicate keywords were added');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should handle keywords with leading or trailing spaces', () => {
|
|
30
|
+
const messyKeywords = [' keyword1', 'keyword2 '];
|
|
31
|
+
layout.keywords(messyKeywords);
|
|
32
|
+
const expectedContent = 'keyword1,keyword2';
|
|
33
|
+
assert.strictEqual(layout.root.$('meta[name="keywords"]').getAttribute('content'), expectedContent, 'Keywords with spaces not trimmed correctly');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should add keywords correctly', () => {
|
|
37
|
+
const keywords = ['keyword1', 'keyword2'];
|
|
38
|
+
layout.keywords(keywords);
|
|
39
|
+
assert(layout.root.$('meta[name="keywords"]').getAttribute('content') === keywords.join(), 'Keywords not set correctly');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle empty keywords array', () => {
|
|
43
|
+
layout.keywords([]);
|
|
44
|
+
assert(!layout.root.$('meta[name="keywords"]'), 'Meta tag for empty keywords should not be created');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it, beforeEach } = require('node:test')
|
|
3
|
+
const Layout = require('../index');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
describe('Link', () => {
|
|
7
|
+
let layout;
|
|
8
|
+
beforeEach(() => layout = new Layout());
|
|
9
|
+
|
|
10
|
+
it('should add a new link element without version', () => {
|
|
11
|
+
const href = 'style.css';
|
|
12
|
+
layout.link(href);
|
|
13
|
+
assert.strictEqual(layout.root.$('link[rel="stylesheet"]').getAttribute('href'), href, 'Link href should match the provided href');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should add a new link element with version', () => {
|
|
17
|
+
const href = 'style.css';
|
|
18
|
+
const version = '1.0';
|
|
19
|
+
layout.link(href, version);
|
|
20
|
+
assert.strictEqual(layout.root.$('link[rel="stylesheet"]').getAttribute('href'), `${href}?v=${version}`, 'Link href should include version query parameter');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should not add a link if one with the same href and version already exists', () => {
|
|
24
|
+
const href = 'style.css';
|
|
25
|
+
const version = '1.0';
|
|
26
|
+
layout.link(href, version);
|
|
27
|
+
layout.link(href, version);
|
|
28
|
+
assert.strictEqual(layout.root.$$(`link[rel="stylesheet"][href="${href}?v=${version}"]`).length, 1, 'Should not add duplicate link with the same version');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should handle invalid href or version correctly', () => {
|
|
32
|
+
layout.link('', '1.0');
|
|
33
|
+
layout.link(null, '1.0');
|
|
34
|
+
// layout.link('style.css', '');
|
|
35
|
+
// layout.link('style.css', null);
|
|
36
|
+
assert.strictEqual(layout.root.$('link[rel="stylesheet"]'), null, 'Should not add a link when href or version are invalid');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should add link correctly', () => {
|
|
40
|
+
const href = 'style.css';
|
|
41
|
+
layout.link(href);
|
|
42
|
+
assert.strictEqual(layout.root.$('link[rel="stylesheet"]').getAttribute('href'), href, 'Link not set correctly');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should not add a new link element if one already exists with the same href and no version', () => {
|
|
46
|
+
const href = 'style.css';
|
|
47
|
+
layout.link(href); // Добавление ссылки без версии
|
|
48
|
+
layout.link(href); // Повторное добавление той же ссылки без версии
|
|
49
|
+
assert.strictEqual(layout.root.$$(`link[rel="stylesheet"][href="${href}"]`).length, 1, 'Should not add duplicate link without version');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should not add a link if href is undefined or null', () => {
|
|
53
|
+
layout.link(undefined, '1.0');
|
|
54
|
+
layout.link(null);
|
|
55
|
+
assert.strictEqual(layout.root.$('link[rel="stylesheet"]'), null, 'Should not add a link when href is undefined or null');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should handle different versions for the same href', () => {
|
|
59
|
+
const href = 'style.css';
|
|
60
|
+
const version1 = '1.0';
|
|
61
|
+
const version2 = '1.1';
|
|
62
|
+
layout.link(href, version1);
|
|
63
|
+
layout.link(href, version2);
|
|
64
|
+
assert.strictEqual(layout.root.$$(`link[rel="stylesheet"]`).length, 2, 'Should add different links for different versions');
|
|
65
|
+
assert.strictEqual(layout.root.$$(`link[rel="stylesheet"][href="${href}?v=${version1}"]`).length, 1, 'First version link should exist');
|
|
66
|
+
assert.strictEqual(layout.root.$$(`link[rel="stylesheet"][href="${href}?v=${version2}"]`).length, 1, 'Second version link should exist');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should correctly add version when href already has parameters', () => {
|
|
70
|
+
const href = 'style.css?param=value';
|
|
71
|
+
const version = '1.0';
|
|
72
|
+
layout.link(href, version);
|
|
73
|
+
assert.strictEqual(layout.root.$('link[rel="stylesheet"]').getAttribute('href'), `${href}&v=${version}`, 'Href should include version appended with &');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should not add a link when one with a similar href prefix exists', () => {
|
|
77
|
+
const href = 'style.css';
|
|
78
|
+
layout.link(href);
|
|
79
|
+
layout.link(href + '?param=value', '1.0');
|
|
80
|
+
assert.strictEqual(layout.root.$$(`link[rel="stylesheet"]`).length, 2, 'Should recognize different full hrefs as different links');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
})
|