astro-helmet 0.1.2 → 0.2.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
@@ -58,8 +58,9 @@ Then add the rendered `head` string to your Astro layout:
58
58
 
59
59
  | priority | item |
60
60
  | -------- | --------------------------------------------- |
61
- | \-3 | `<meta charset="">` |
62
- | \-2 | `<meta name="viewport">` |
61
+ | \-4 | `<meta charset="">` |
62
+ | \-3 | `<meta name="viewport">` |
63
+ | \-2 | `<base ="">` |
63
64
  | \-1 | `<meta http-equiv="">` |
64
65
  | 0 | `<title>` |
65
66
  | 10 | `<link rel="preconnect" />` |
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ type ContentItem = BaseItem & {
7
7
  };
8
8
  export type HeadItems = {
9
9
  title?: string;
10
+ base?: BaseItem[];
10
11
  meta?: BaseItem[];
11
12
  link?: BaseItem[];
12
13
  style?: ContentItem[];
package/dist/index.js CHANGED
@@ -3,12 +3,20 @@ const DEFAULT_VIEWPORT = {
3
3
  name: 'viewport',
4
4
  content: 'width=device-width, initial-scale=1'
5
5
  };
6
+ const tagNames = [
7
+ 'base',
8
+ 'meta',
9
+ 'link',
10
+ 'style',
11
+ 'script',
12
+ 'noscript'
13
+ ];
14
+ // TODO: takes either a single HeadItems object or an array of HeadItems
6
15
  export function renderHead(headItems) {
7
16
  const items = mergeHeadItems(headItems);
8
17
  if (!items.title?.length)
9
18
  throw new Error('Missing title tag.');
10
19
  const tags = [];
11
- const tagNames = ['meta', 'link', 'style', 'script', 'noscript'];
12
20
  tagNames.forEach((tag) => {
13
21
  tags.push(...items[tag].map((item) => ({ ...item, tagName: tag })));
14
22
  });
@@ -30,6 +38,7 @@ export function renderHead(headItems) {
30
38
  function mergeHeadItems(items) {
31
39
  const mergedHeadItems = {
32
40
  title: '',
41
+ base: [],
33
42
  meta: [],
34
43
  link: [],
35
44
  style: [],
@@ -39,6 +48,8 @@ function mergeHeadItems(items) {
39
48
  items.forEach((item) => {
40
49
  if (item.title && item.title.length)
41
50
  mergedHeadItems.title = item.title;
51
+ if (item.base)
52
+ mergedHeadItems.base.push(...item.base);
42
53
  if (item.meta)
43
54
  mergedHeadItems.meta.push(...item.meta);
44
55
  if (item.link)
@@ -51,6 +62,7 @@ function mergeHeadItems(items) {
51
62
  mergedHeadItems.noscript.push(...item.noscript);
52
63
  });
53
64
  mergedHeadItems.meta = deduplicateMetaItems(mergedHeadItems.meta);
65
+ mergedHeadItems.base = mergedHeadItems.base.slice(-1);
54
66
  return mergedHeadItems;
55
67
  }
56
68
  function applyDefaultPriorities(tags) {
@@ -65,11 +77,14 @@ function applyDefaultPriorities(tags) {
65
77
  unprioritisedTags.forEach((tag) => {
66
78
  let priority;
67
79
  switch (tag.tagName) {
80
+ case 'base':
81
+ priority = -2;
82
+ break;
68
83
  case 'meta':
69
84
  if (tag.charset)
70
- priority = -3;
85
+ priority = -4;
71
86
  else if (tag.name === 'viewport')
72
- priority = -2;
87
+ priority = -3;
73
88
  else if (tag['http-equiv'])
74
89
  priority = -1;
75
90
  else
@@ -107,9 +122,9 @@ function applyDefaultPriorities(tags) {
107
122
  }
108
123
  function applyDefaultTags(tags) {
109
124
  if (!tags.some((tag) => tag.tagName === 'meta' && tag.charset))
110
- tags.push({ ...DEFAULT_CHARSET, tagName: 'meta', priority: -3 });
125
+ tags.push({ ...DEFAULT_CHARSET, tagName: 'meta', priority: -4 });
111
126
  if (!tags.some((tag) => tag.tagName === 'meta' && tag.name === 'viewport'))
112
- tags.push({ ...DEFAULT_VIEWPORT, tagName: 'meta', priority: -2 });
127
+ tags.push({ ...DEFAULT_VIEWPORT, tagName: 'meta', priority: -3 });
113
128
  return tags;
114
129
  }
115
130
  function deduplicateMetaItems(metaItems) {
@@ -123,8 +138,8 @@ function deduplicateMetaItems(metaItems) {
123
138
  }
124
139
  function renderHeadTag(item) {
125
140
  const attrs = renderAttrs(item);
126
- return ['meta', 'link'].includes(item.tagName)
127
- ? `<${item.tagName} ${attrs} />`
141
+ return ['meta', 'link', 'base'].includes(item.tagName)
142
+ ? `<${item.tagName} ${attrs}>`
128
143
  : `<${item.tagName}${attrs && ' '}${attrs}>${item.innerHTML || ''}</${item.tagName}>`;
129
144
  }
130
145
  export function renderAttrs(item) {
@@ -133,10 +148,21 @@ export function renderAttrs(item) {
133
148
  .map(([key, value]) => {
134
149
  if (typeof value === 'boolean')
135
150
  return value ? key : '';
151
+ else if (value === null || value === undefined)
152
+ return '';
136
153
  else
137
- return `${key}="${value}"`;
154
+ return `${key}="${escapeHtml(String(value))}"`;
138
155
  })
139
156
  .filter((attr) => attr !== '')
140
157
  .join(' ');
141
158
  }
159
+ function escapeHtml(str) {
160
+ const escapeMap = {
161
+ '"': '&quot;',
162
+ '&': '&amp;',
163
+ '<': '&lt;',
164
+ '>': '&gt;'
165
+ };
166
+ return str.replace(/["&<>]/g, (match) => escapeMap[match] || match);
167
+ }
142
168
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;AAC5C,MAAM,gBAAgB,GAAG;IACxB,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,qCAAqC;CAC9C,CAAA;AAGD,MAAM,QAAQ,GAAc;IAC3B,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,QAAQ;IACR,UAAU;CACV,CAAA;AA+BD,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,SAAsB;IAChD,MAAM,KAAK,GAAG,cAAc,CAAC,SAAS,CAAC,CAAA;IACvC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;IAE/D,MAAM,IAAI,GAAU,EAAE,CAAA;IACtB,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACxB,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,IAAI,eAAe,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAA;IAClD,eAAe,GAAG,gBAAgB,CAAC,eAAe,CAAC,CAAA;IAEnD,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAA;IAE3E,MAAM,YAAY,GAAG,WAAW;SAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;SAC7B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;IACpC,MAAM,aAAa,GAAG,WAAW;SAC/B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;SAC7B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;IAEpC,OAAO;QACN,GAAG,YAAY;QACf,UAAU,KAAK,CAAC,KAAK,UAAU;QAC/B,GAAG,aAAa;KAChB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACb,CAAC;AAED,SAAS,cAAc,CAAC,KAAkB;IACzC,MAAM,eAAe,GAAoB;QACxC,KAAK,EAAE,EAAE;QACT,IAAI,EAAE,EAAE;QACR,IAAI,EAAE,EAAE;QACR,IAAI,EAAE,EAAE;QACR,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE,EAAE;KACZ,CAAA;IAED,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACtB,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,eAAe,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QACvE,IAAI,IAAI,CAAC,IAAI;YAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAA;QACtD,IAAI,IAAI,CAAC,IAAI;YAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAA;QACtD,IAAI,IAAI,CAAC,IAAI;YAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAA;QACtD,IAAI,IAAI,CAAC,KAAK;YAAE,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;QACzD,IAAI,IAAI,CAAC,MAAM;YAAE,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;QAC5D,IAAI,IAAI,CAAC,QAAQ;YAAE,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;IAEF,eAAe,CAAC,IAAI,GAAG,oBAAoB,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IACjE,eAAe,CAAC,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAErD,OAAO,eAAe,CAAA;AACvB,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAW;IAC1C,MAAM,eAAe,GAAqB,EAAE,CAAA;IAC5C,MAAM,iBAAiB,GAAU,EAAE,CAAA;IAEnC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACpB,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;YAAE,eAAe,CAAC,IAAI,CAAC,GAAqB,CAAC,CAAA;;YACtE,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,iBAAiB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACjC,IAAI,QAAgB,CAAA;QACpB,QAAQ,GAAG,CAAC,OAAO,EAAE,CAAC;YACrB,KAAK,MAAM;gBACV,QAAQ,GAAG,CAAC,CAAC,CAAA;gBACb,MAAK;YACN,KAAK,MAAM;gBACV,IAAI,GAAG,CAAC,OAAO;oBAAE,QAAQ,GAAG,CAAC,CAAC,CAAA;qBACzB,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU;oBAAE,QAAQ,GAAG,CAAC,CAAC,CAAA;qBAC1C,IAAI,GAAG,CAAC,YAAY,CAAC;oBAAE,QAAQ,GAAG,CAAC,CAAC,CAAA;;oBACpC,QAAQ,GAAG,GAAG,CAAA;gBACnB,MAAK;YAEN,KAAK,MAAM;gBACV,IAAI,GAAG,CAAC,GAAG,KAAK,YAAY;oBAAE,QAAQ,GAAG,EAAE,CAAA;qBACtC,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS;oBAAE,QAAQ,GAAG,EAAE,CAAA;qBACxC,IAAI,GAAG,CAAC,GAAG,KAAK,UAAU;oBAAE,QAAQ,GAAG,EAAE,CAAA;qBACzC,IAAI,GAAG,CAAC,GAAG,KAAK,YAAY;oBAAE,QAAQ,GAAG,EAAE,CAAA;;oBAC3C,QAAQ,GAAG,EAAE,CAAA;gBAClB,MAAK;YAEN,KAAK,OAAO;gBACX,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;gBACtD,MAAK;YAEN,KAAK,QAAQ;gBACZ,IAAI,GAAG,CAAC,KAAK;oBAAE,QAAQ,GAAG,EAAE,CAAA;qBACvB,IAAI,GAAG,CAAC,KAAK;oBAAE,QAAQ,GAAG,EAAE,CAAA;;oBAC5B,QAAQ,GAAG,EAAE,CAAA;gBAClB,MAAK;YAEN;gBACC,QAAQ,GAAG,GAAG,CAAA;QAChB,CAAC;QACD,eAAe,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,OAAO,eAAe,CAAA;AACvB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAsB;IAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,KAAK,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC;QAC7D,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;IAEjE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,CAAC;QACzE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;IAElE,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,SAAS,oBAAoB,CAAC,SAAqB;IAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAA;IAE3C,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,CAAA;QAC5D,IAAI,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;AACpC,CAAC;AAED,SAAS,aAAa,CAAC,IAA4B;IAClD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC/B,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;QACrD,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,KAAK,GAAG;QAC9B,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,KAAK,IAAI,GAAG,GAAG,KAAK,IAAI,IAAI,CAAC,SAAS,IAAI,EAAE,KAC/D,IAAI,CAAC,OACN,GAAG,CAAA;AACN,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAA4B;IACvD,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;SACzB,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;SACtE,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACrB,IAAI,OAAO,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;aAClD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,EAAE,CAAA;;YACpD,OAAO,GAAG,GAAG,KAAK,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAA;IACpD,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;SAC7B,IAAI,CAAC,GAAG,CAAC,CAAA;AACZ,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC9B,MAAM,SAAS,GAA2B;QACzC,GAAG,EAAE,QAAQ;QACb,GAAG,EAAE,OAAO;QACZ,GAAG,EAAE,MAAM;QACX,GAAG,EAAE,MAAM;KACX,CAAA;IAED,OAAO,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAA;AACpE,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ import { renderAttrs } from './index';
2
+ import { describe, it, expect } from 'vitest';
3
+ const testCases = [
4
+ {
5
+ description: 'Preload a font with standard attributes',
6
+ attributes: {
7
+ rel: 'preload',
8
+ as: 'font',
9
+ type: 'font/woff2',
10
+ crossorigin: 'anonymous',
11
+ href: '/fonts/InterVariable.woff2'
12
+ },
13
+ expected: 'rel="preload" as="font" type="font/woff2" crossorigin="anonymous" href="/fonts/InterVariable.woff2"'
14
+ },
15
+ {
16
+ description: 'Link a stylesheet with standard attributes',
17
+ attributes: {
18
+ rel: 'stylesheet',
19
+ href: '/css/styles.css'
20
+ },
21
+ expected: 'rel="stylesheet" href="/css/styles.css"'
22
+ },
23
+ {
24
+ description: "Preload font with missing 'as' attribute",
25
+ attributes: {
26
+ rel: 'preload',
27
+ type: 'font/woff2',
28
+ crossorigin: 'anonymous',
29
+ href: '/fonts/InterVariable.woff2'
30
+ },
31
+ expected: 'rel="preload" type="font/woff2" crossorigin="anonymous" href="/fonts/InterVariable.woff2"'
32
+ },
33
+ {
34
+ description: 'Use non-standard rel attribute',
35
+ attributes: {
36
+ rel: 'data',
37
+ href: '/data/config.json'
38
+ },
39
+ expected: 'rel="data" href="/data/config.json"'
40
+ },
41
+ {
42
+ description: 'Link tag with invalid type attribute for CSS',
43
+ attributes: {
44
+ rel: 'stylesheet',
45
+ type: 'text/plain',
46
+ href: '/css/styles.css'
47
+ },
48
+ expected: 'rel="stylesheet" type="text/plain" href="/css/styles.css"'
49
+ },
50
+ {
51
+ description: 'Attributes with boolean values',
52
+ attributes: {
53
+ async: true,
54
+ defer: false,
55
+ src: '/js/script.js'
56
+ },
57
+ expected: 'async src="/js/script.js"'
58
+ },
59
+ {
60
+ description: 'Attributes with special characters',
61
+ attributes: {
62
+ 'data-test': 'value with spaces',
63
+ 'data-test2': 'value with "quotes"',
64
+ src: '/js/script.js'
65
+ },
66
+ expected: 'data-test="value with spaces" data-test2="value with &quot;quotes&quot;" src="/js/script.js"'
67
+ },
68
+ {
69
+ description: 'Attributes with empty values',
70
+ attributes: {
71
+ rel: '',
72
+ href: '/css/styles.css'
73
+ },
74
+ expected: 'rel="" href="/css/styles.css"'
75
+ },
76
+ {
77
+ description: 'Attributes with undefined values',
78
+ attributes: {
79
+ rel: undefined,
80
+ href: '/css/styles.css'
81
+ },
82
+ expected: 'href="/css/styles.css"'
83
+ },
84
+ {
85
+ description: 'Attributes with null values',
86
+ attributes: {
87
+ rel: null,
88
+ href: '/css/styles.css'
89
+ },
90
+ expected: 'href="/css/styles.css"'
91
+ },
92
+ {
93
+ description: 'Attributes with number values',
94
+ attributes: {
95
+ width: 100,
96
+ height: 200,
97
+ src: '/img/image.jpg'
98
+ },
99
+ expected: 'width="100" height="200" src="/img/image.jpg"'
100
+ }
101
+ ];
102
+ describe('renderAttrs', () => {
103
+ testCases.forEach(({ description, attributes, expected }) => {
104
+ it(description, () => {
105
+ expect(renderAttrs(attributes)).toEqual(expected);
106
+ });
107
+ });
108
+ });
109
+ //# sourceMappingURL=renderAttrs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderAttrs.test.js","sourceRoot":"","sources":["../src/renderAttrs.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE7C,MAAM,SAAS,GAAG;IACjB;QACC,WAAW,EAAE,yCAAyC;QACtD,UAAU,EAAE;YACX,GAAG,EAAE,SAAS;YACd,EAAE,EAAE,MAAM;YACV,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,WAAW;YACxB,IAAI,EAAE,4BAA4B;SAClC;QACD,QAAQ,EACP,qGAAqG;KACtG;IACD;QACC,WAAW,EAAE,4CAA4C;QACzD,UAAU,EAAE;YACX,GAAG,EAAE,YAAY;YACjB,IAAI,EAAE,iBAAiB;SACvB;QACD,QAAQ,EAAE,yCAAyC;KACnD;IACD;QACC,WAAW,EAAE,0CAA0C;QACvD,UAAU,EAAE;YACX,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,WAAW;YACxB,IAAI,EAAE,4BAA4B;SAClC;QACD,QAAQ,EACP,2FAA2F;KAC5F;IACD;QACC,WAAW,EAAE,gCAAgC;QAC7C,UAAU,EAAE;YACX,GAAG,EAAE,MAAM;YACX,IAAI,EAAE,mBAAmB;SACzB;QACD,QAAQ,EAAE,qCAAqC;KAC/C;IACD;QACC,WAAW,EAAE,8CAA8C;QAC3D,UAAU,EAAE;YACX,GAAG,EAAE,YAAY;YACjB,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,iBAAiB;SACvB;QACD,QAAQ,EAAE,2DAA2D;KACrE;IACD;QACC,WAAW,EAAE,gCAAgC;QAC7C,UAAU,EAAE;YACX,KAAK,EAAE,IAAI;YACX,KAAK,EAAE,KAAK;YACZ,GAAG,EAAE,eAAe;SACpB;QACD,QAAQ,EAAE,2BAA2B;KACrC;IACD;QACC,WAAW,EAAE,oCAAoC;QACjD,UAAU,EAAE;YACX,WAAW,EAAE,mBAAmB;YAChC,YAAY,EAAE,qBAAqB;YACnC,GAAG,EAAE,eAAe;SACpB;QACD,QAAQ,EACP,8FAA8F;KAC/F;IACD;QACC,WAAW,EAAE,8BAA8B;QAC3C,UAAU,EAAE;YACX,GAAG,EAAE,EAAE;YACP,IAAI,EAAE,iBAAiB;SACvB;QACD,QAAQ,EAAE,+BAA+B;KACzC;IACD;QACC,WAAW,EAAE,kCAAkC;QAC/C,UAAU,EAAE;YACX,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,iBAAiB;SACvB;QACD,QAAQ,EAAE,wBAAwB;KAClC;IACD;QACC,WAAW,EAAE,6BAA6B;QAC1C,UAAU,EAAE;YACX,GAAG,EAAE,IAAI;YACT,IAAI,EAAE,iBAAiB;SACvB;QACD,QAAQ,EAAE,wBAAwB;KAClC;IACD;QACC,WAAW,EAAE,+BAA+B;QAC5C,UAAU,EAAE;YACX,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,GAAG;YACX,GAAG,EAAE,gBAAgB;SACrB;QACD,QAAQ,EAAE,+CAA+C;KACzD;CACD,CAAA;AAED,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC5B,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC3D,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;YACpB,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAClD,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,124 @@
1
+ import { renderHead } from './index';
2
+ import { describe, it, expect } from 'vitest';
3
+ const testCases = [
4
+ {
5
+ description: 'Render head with minimal content',
6
+ params: [
7
+ {
8
+ title: 'My Site Title'
9
+ }
10
+ ],
11
+ expected: `<meta charset="UTF-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1">
13
+ <title>My Site Title</title>`
14
+ },
15
+ {
16
+ description: 'Render head with base tag',
17
+ params: [
18
+ {
19
+ title: 'My Site Title',
20
+ base: [{ href: 'https://example.com' }]
21
+ }
22
+ ],
23
+ expected: `<meta charset="UTF-8">
24
+ <meta name="viewport" content="width=device-width, initial-scale=1">
25
+ <base href="https://example.com">
26
+ <title>My Site Title</title>`
27
+ },
28
+ {
29
+ description: 'Render head with meta tags',
30
+ params: [
31
+ {
32
+ title: 'My Site Title',
33
+ meta: [{ name: 'description', content: 'My site description' }]
34
+ }
35
+ ],
36
+ expected: `<meta charset="UTF-8">
37
+ <meta name="viewport" content="width=device-width, initial-scale=1">
38
+ <title>My Site Title</title>
39
+ <meta name="description" content="My site description">`
40
+ },
41
+ {
42
+ description: 'Render head with link tags',
43
+ params: [
44
+ {
45
+ title: 'My Site Title',
46
+ link: [{ rel: 'stylesheet', href: 'styles.css' }]
47
+ }
48
+ ],
49
+ expected: `<meta charset="UTF-8">
50
+ <meta name="viewport" content="width=device-width, initial-scale=1">
51
+ <title>My Site Title</title>
52
+ <link rel="stylesheet" href="styles.css">`
53
+ },
54
+ {
55
+ description: 'Render head with style tags',
56
+ params: [
57
+ {
58
+ title: 'My Site Title',
59
+ style: [{ innerHTML: 'body { color: red; }' }]
60
+ }
61
+ ],
62
+ expected: `<meta charset="UTF-8">
63
+ <meta name="viewport" content="width=device-width, initial-scale=1">
64
+ <title>My Site Title</title>
65
+ <style>body { color: red; }</style>`
66
+ },
67
+ {
68
+ description: 'Render head with script tags',
69
+ params: [
70
+ {
71
+ title: 'My Site Title',
72
+ script: [{ innerHTML: 'console.log("Hello, world!")' }]
73
+ }
74
+ ],
75
+ expected: `<meta charset="UTF-8">
76
+ <meta name="viewport" content="width=device-width, initial-scale=1">
77
+ <title>My Site Title</title>
78
+ <script>console.log("Hello, world!")</script>`
79
+ },
80
+ {
81
+ description: 'Render head with noscript tags',
82
+ params: [
83
+ {
84
+ title: 'My Site Title',
85
+ noscript: [{ innerHTML: 'Please enable JavaScript' }]
86
+ }
87
+ ],
88
+ expected: `<meta charset="UTF-8">
89
+ <meta name="viewport" content="width=device-width, initial-scale=1">
90
+ <title>My Site Title</title>
91
+ <noscript>Please enable JavaScript</noscript>`
92
+ },
93
+ {
94
+ description: 'Render head with all tags',
95
+ params: [
96
+ {
97
+ title: 'My Site Title',
98
+ base: [{ href: 'https://example.com' }],
99
+ meta: [{ name: 'description', content: 'My site description' }],
100
+ link: [{ rel: 'stylesheet', href: 'styles.css' }],
101
+ style: [{ innerHTML: 'body { color: red; }' }],
102
+ script: [{ innerHTML: 'console.log("Hello, world!")' }],
103
+ noscript: [{ innerHTML: 'Please enable JavaScript' }]
104
+ }
105
+ ],
106
+ expected: `<meta charset="UTF-8">
107
+ <meta name="viewport" content="width=device-width, initial-scale=1">
108
+ <base href="https://example.com">
109
+ <title>My Site Title</title>
110
+ <script>console.log("Hello, world!")</script>
111
+ <link rel="stylesheet" href="styles.css">
112
+ <style>body { color: red; }</style>
113
+ <meta name="description" content="My site description">
114
+ <noscript>Please enable JavaScript</noscript>`
115
+ }
116
+ ];
117
+ describe('renderHead', () => {
118
+ testCases.forEach(({ description, params, expected }) => {
119
+ it(description, () => {
120
+ expect(renderHead(params)).toEqual(expected);
121
+ });
122
+ });
123
+ });
124
+ //# sourceMappingURL=renderHead.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderHead.test.js","sourceRoot":"","sources":["../src/renderHead.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "astro-helmet",
3
3
  "type": "module",
4
- "version": "0.1.2",
4
+ "version": "0.2.0",
5
5
  "description": "A document head manager for astro.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "scripts": {
9
9
  "build": "tsc",
10
- "test": "vitest"
10
+ "test": "vitest",
11
+ "coverage": "vitest run --coverage"
11
12
  },
12
13
  "keywords": [
13
14
  "astro",
@@ -22,6 +23,7 @@
22
23
  "author": "Ryan Voitiskis",
23
24
  "license": "ISC",
24
25
  "devDependencies": {
26
+ "@vitest/coverage-v8": "^1.6.0",
25
27
  "prettier": "^3.3.0",
26
28
  "typescript": "^5.4.5",
27
29
  "vitest": "^1.6.0"
package/src/index.ts CHANGED
@@ -4,7 +4,15 @@ const DEFAULT_VIEWPORT = {
4
4
  content: 'width=device-width, initial-scale=1'
5
5
  }
6
6
 
7
- type TagName = 'meta' | 'link' | 'style' | 'script' | 'noscript'
7
+ type TagName = 'base' | 'meta' | 'link' | 'style' | 'script' | 'noscript'
8
+ const tagNames: TagName[] = [
9
+ 'base',
10
+ 'meta',
11
+ 'link',
12
+ 'style',
13
+ 'script',
14
+ 'noscript'
15
+ ]
8
16
 
9
17
  type BaseItem = {
10
18
  [key: string]: any
@@ -25,6 +33,7 @@ type PrioritisedTag = Tag & {
25
33
 
26
34
  export type HeadItems = {
27
35
  title?: string
36
+ base?: BaseItem[]
28
37
  meta?: BaseItem[]
29
38
  link?: BaseItem[]
30
39
  style?: ContentItem[]
@@ -34,12 +43,12 @@ export type HeadItems = {
34
43
 
35
44
  type MergedHeadItems = Required<HeadItems>
36
45
 
46
+ // TODO: takes either a single HeadItems object or an array of HeadItems
37
47
  export function renderHead(headItems: HeadItems[]): string {
38
48
  const items = mergeHeadItems(headItems)
39
49
  if (!items.title?.length) throw new Error('Missing title tag.')
40
50
 
41
51
  const tags: Tag[] = []
42
- const tagNames: TagName[] = ['meta', 'link', 'style', 'script', 'noscript']
43
52
  tagNames.forEach((tag) => {
44
53
  tags.push(...items[tag].map((item) => ({ ...item, tagName: tag })))
45
54
  })
@@ -66,6 +75,7 @@ export function renderHead(headItems: HeadItems[]): string {
66
75
  function mergeHeadItems(items: HeadItems[]): MergedHeadItems {
67
76
  const mergedHeadItems: MergedHeadItems = {
68
77
  title: '',
78
+ base: [],
69
79
  meta: [],
70
80
  link: [],
71
81
  style: [],
@@ -75,6 +85,7 @@ function mergeHeadItems(items: HeadItems[]): MergedHeadItems {
75
85
 
76
86
  items.forEach((item) => {
77
87
  if (item.title && item.title.length) mergedHeadItems.title = item.title
88
+ if (item.base) mergedHeadItems.base.push(...item.base)
78
89
  if (item.meta) mergedHeadItems.meta.push(...item.meta)
79
90
  if (item.link) mergedHeadItems.link.push(...item.link)
80
91
  if (item.style) mergedHeadItems.style.push(...item.style)
@@ -83,6 +94,7 @@ function mergeHeadItems(items: HeadItems[]): MergedHeadItems {
83
94
  })
84
95
 
85
96
  mergedHeadItems.meta = deduplicateMetaItems(mergedHeadItems.meta)
97
+ mergedHeadItems.base = mergedHeadItems.base.slice(-1)
86
98
 
87
99
  return mergedHeadItems
88
100
  }
@@ -99,9 +111,12 @@ function applyDefaultPriorities(tags: Tag[]): PrioritisedTag[] {
99
111
  unprioritisedTags.forEach((tag) => {
100
112
  let priority: number
101
113
  switch (tag.tagName) {
114
+ case 'base':
115
+ priority = -2
116
+ break
102
117
  case 'meta':
103
- if (tag.charset) priority = -3
104
- else if (tag.name === 'viewport') priority = -2
118
+ if (tag.charset) priority = -4
119
+ else if (tag.name === 'viewport') priority = -3
105
120
  else if (tag['http-equiv']) priority = -1
106
121
  else priority = 100
107
122
  break
@@ -135,10 +150,10 @@ function applyDefaultPriorities(tags: Tag[]): PrioritisedTag[] {
135
150
 
136
151
  function applyDefaultTags(tags: PrioritisedTag[]): PrioritisedTag[] {
137
152
  if (!tags.some((tag) => tag.tagName === 'meta' && tag.charset))
138
- tags.push({ ...DEFAULT_CHARSET, tagName: 'meta', priority: -3 })
153
+ tags.push({ ...DEFAULT_CHARSET, tagName: 'meta', priority: -4 })
139
154
 
140
155
  if (!tags.some((tag) => tag.tagName === 'meta' && tag.name === 'viewport'))
141
- tags.push({ ...DEFAULT_VIEWPORT, tagName: 'meta', priority: -2 })
156
+ tags.push({ ...DEFAULT_VIEWPORT, tagName: 'meta', priority: -3 })
142
157
 
143
158
  return tags
144
159
  }
@@ -156,8 +171,8 @@ function deduplicateMetaItems(metaItems: BaseItem[]): BaseItem[] {
156
171
 
157
172
  function renderHeadTag(item: BaseItem | ContentItem): string {
158
173
  const attrs = renderAttrs(item)
159
- return ['meta', 'link'].includes(item.tagName)
160
- ? `<${item.tagName} ${attrs} />`
174
+ return ['meta', 'link', 'base'].includes(item.tagName)
175
+ ? `<${item.tagName} ${attrs}>`
161
176
  : `<${item.tagName}${attrs && ' '}${attrs}>${item.innerHTML || ''}</${
162
177
  item.tagName
163
178
  }>`
@@ -168,8 +183,20 @@ export function renderAttrs(item: BaseItem | ContentItem): string {
168
183
  .filter(([key]) => !['innerHTML', 'priority', 'tagName'].includes(key))
169
184
  .map(([key, value]) => {
170
185
  if (typeof value === 'boolean') return value ? key : ''
171
- else return `${key}="${value}"`
186
+ else if (value === null || value === undefined) return ''
187
+ else return `${key}="${escapeHtml(String(value))}"`
172
188
  })
173
189
  .filter((attr) => attr !== '')
174
190
  .join(' ')
175
191
  }
192
+
193
+ function escapeHtml(str: string): string {
194
+ const escapeMap: Record<string, string> = {
195
+ '"': '&quot;',
196
+ '&': '&amp;',
197
+ '<': '&lt;',
198
+ '>': '&gt;'
199
+ }
200
+
201
+ return str.replace(/["&<>]/g, (match) => escapeMap[match] || match)
202
+ }
@@ -0,0 +1,113 @@
1
+ import { renderAttrs } from './index'
2
+ import { describe, it, expect } from 'vitest'
3
+
4
+ const testCases = [
5
+ {
6
+ description: 'Preload a font with standard attributes',
7
+ attributes: {
8
+ rel: 'preload',
9
+ as: 'font',
10
+ type: 'font/woff2',
11
+ crossorigin: 'anonymous',
12
+ href: '/fonts/InterVariable.woff2'
13
+ },
14
+ expected:
15
+ 'rel="preload" as="font" type="font/woff2" crossorigin="anonymous" href="/fonts/InterVariable.woff2"'
16
+ },
17
+ {
18
+ description: 'Link a stylesheet with standard attributes',
19
+ attributes: {
20
+ rel: 'stylesheet',
21
+ href: '/css/styles.css'
22
+ },
23
+ expected: 'rel="stylesheet" href="/css/styles.css"'
24
+ },
25
+ {
26
+ description: "Preload font with missing 'as' attribute",
27
+ attributes: {
28
+ rel: 'preload',
29
+ type: 'font/woff2',
30
+ crossorigin: 'anonymous',
31
+ href: '/fonts/InterVariable.woff2'
32
+ },
33
+ expected:
34
+ 'rel="preload" type="font/woff2" crossorigin="anonymous" href="/fonts/InterVariable.woff2"'
35
+ },
36
+ {
37
+ description: 'Use non-standard rel attribute',
38
+ attributes: {
39
+ rel: 'data',
40
+ href: '/data/config.json'
41
+ },
42
+ expected: 'rel="data" href="/data/config.json"'
43
+ },
44
+ {
45
+ description: 'Link tag with invalid type attribute for CSS',
46
+ attributes: {
47
+ rel: 'stylesheet',
48
+ type: 'text/plain',
49
+ href: '/css/styles.css'
50
+ },
51
+ expected: 'rel="stylesheet" type="text/plain" href="/css/styles.css"'
52
+ },
53
+ {
54
+ description: 'Attributes with boolean values',
55
+ attributes: {
56
+ async: true,
57
+ defer: false,
58
+ src: '/js/script.js'
59
+ },
60
+ expected: 'async src="/js/script.js"'
61
+ },
62
+ {
63
+ description: 'Attributes with special characters',
64
+ attributes: {
65
+ 'data-test': 'value with spaces',
66
+ 'data-test2': 'value with "quotes"',
67
+ src: '/js/script.js'
68
+ },
69
+ expected:
70
+ 'data-test="value with spaces" data-test2="value with &quot;quotes&quot;" src="/js/script.js"'
71
+ },
72
+ {
73
+ description: 'Attributes with empty values',
74
+ attributes: {
75
+ rel: '',
76
+ href: '/css/styles.css'
77
+ },
78
+ expected: 'rel="" href="/css/styles.css"'
79
+ },
80
+ {
81
+ description: 'Attributes with undefined values',
82
+ attributes: {
83
+ rel: undefined,
84
+ href: '/css/styles.css'
85
+ },
86
+ expected: 'href="/css/styles.css"'
87
+ },
88
+ {
89
+ description: 'Attributes with null values',
90
+ attributes: {
91
+ rel: null,
92
+ href: '/css/styles.css'
93
+ },
94
+ expected: 'href="/css/styles.css"'
95
+ },
96
+ {
97
+ description: 'Attributes with number values',
98
+ attributes: {
99
+ width: 100,
100
+ height: 200,
101
+ src: '/img/image.jpg'
102
+ },
103
+ expected: 'width="100" height="200" src="/img/image.jpg"'
104
+ }
105
+ ]
106
+
107
+ describe('renderAttrs', () => {
108
+ testCases.forEach(({ description, attributes, expected }) => {
109
+ it(description, () => {
110
+ expect(renderAttrs(attributes)).toEqual(expected)
111
+ })
112
+ })
113
+ })
@@ -0,0 +1,131 @@
1
+ import { renderHead, type HeadItems } from './index'
2
+ import { describe, it, expect } from 'vitest'
3
+
4
+ type TestCase = {
5
+ description: string
6
+ params: HeadItems[]
7
+ expected: string
8
+ }
9
+
10
+ const testCases: TestCase[] = [
11
+ {
12
+ description: 'Render head with minimal content',
13
+ params: [
14
+ {
15
+ title: 'My Site Title'
16
+ }
17
+ ],
18
+ expected: `<meta charset="UTF-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1">
20
+ <title>My Site Title</title>`
21
+ },
22
+ {
23
+ description: 'Render head with base tag',
24
+ params: [
25
+ {
26
+ title: 'My Site Title',
27
+ base: [{ href: 'https://example.com' }]
28
+ }
29
+ ],
30
+ expected: `<meta charset="UTF-8">
31
+ <meta name="viewport" content="width=device-width, initial-scale=1">
32
+ <base href="https://example.com">
33
+ <title>My Site Title</title>`
34
+ },
35
+ {
36
+ description: 'Render head with meta tags',
37
+ params: [
38
+ {
39
+ title: 'My Site Title',
40
+ meta: [{ name: 'description', content: 'My site description' }]
41
+ }
42
+ ],
43
+ expected: `<meta charset="UTF-8">
44
+ <meta name="viewport" content="width=device-width, initial-scale=1">
45
+ <title>My Site Title</title>
46
+ <meta name="description" content="My site description">`
47
+ },
48
+ {
49
+ description: 'Render head with link tags',
50
+ params: [
51
+ {
52
+ title: 'My Site Title',
53
+ link: [{ rel: 'stylesheet', href: 'styles.css' }]
54
+ }
55
+ ],
56
+ expected: `<meta charset="UTF-8">
57
+ <meta name="viewport" content="width=device-width, initial-scale=1">
58
+ <title>My Site Title</title>
59
+ <link rel="stylesheet" href="styles.css">`
60
+ },
61
+ {
62
+ description: 'Render head with style tags',
63
+ params: [
64
+ {
65
+ title: 'My Site Title',
66
+ style: [{ innerHTML: 'body { color: red; }' }]
67
+ }
68
+ ],
69
+ expected: `<meta charset="UTF-8">
70
+ <meta name="viewport" content="width=device-width, initial-scale=1">
71
+ <title>My Site Title</title>
72
+ <style>body { color: red; }</style>`
73
+ },
74
+ {
75
+ description: 'Render head with script tags',
76
+ params: [
77
+ {
78
+ title: 'My Site Title',
79
+ script: [{ innerHTML: 'console.log("Hello, world!")' }]
80
+ }
81
+ ],
82
+ expected: `<meta charset="UTF-8">
83
+ <meta name="viewport" content="width=device-width, initial-scale=1">
84
+ <title>My Site Title</title>
85
+ <script>console.log("Hello, world!")</script>`
86
+ },
87
+ {
88
+ description: 'Render head with noscript tags',
89
+ params: [
90
+ {
91
+ title: 'My Site Title',
92
+ noscript: [{ innerHTML: 'Please enable JavaScript' }]
93
+ }
94
+ ],
95
+ expected: `<meta charset="UTF-8">
96
+ <meta name="viewport" content="width=device-width, initial-scale=1">
97
+ <title>My Site Title</title>
98
+ <noscript>Please enable JavaScript</noscript>`
99
+ },
100
+ {
101
+ description: 'Render head with all tags',
102
+ params: [
103
+ {
104
+ title: 'My Site Title',
105
+ base: [{ href: 'https://example.com' }],
106
+ meta: [{ name: 'description', content: 'My site description' }],
107
+ link: [{ rel: 'stylesheet', href: 'styles.css' }],
108
+ style: [{ innerHTML: 'body { color: red; }' }],
109
+ script: [{ innerHTML: 'console.log("Hello, world!")' }],
110
+ noscript: [{ innerHTML: 'Please enable JavaScript' }]
111
+ }
112
+ ],
113
+ expected: `<meta charset="UTF-8">
114
+ <meta name="viewport" content="width=device-width, initial-scale=1">
115
+ <base href="https://example.com">
116
+ <title>My Site Title</title>
117
+ <script>console.log("Hello, world!")</script>
118
+ <link rel="stylesheet" href="styles.css">
119
+ <style>body { color: red; }</style>
120
+ <meta name="description" content="My site description">
121
+ <noscript>Please enable JavaScript</noscript>`
122
+ }
123
+ ]
124
+
125
+ describe('renderHead', () => {
126
+ testCases.forEach(({ description, params, expected }) => {
127
+ it(description, () => {
128
+ expect(renderHead(params)).toEqual(expected)
129
+ })
130
+ })
131
+ })
package/src/index.test.ts DELETED
@@ -1,72 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { renderHead, renderAttrs, HeadItems } from './index'
3
-
4
- describe('renderHead', () => {
5
- it('should render the head tags in the correct order', () => {
6
- const headItems: HeadItems[] = [
7
- {
8
- title: 'My Page',
9
- meta: [{ name: 'description', content: 'My page description' }],
10
- link: [{ rel: 'stylesheet', href: '/styles.css' }],
11
- script: [{ src: '/script.js' }]
12
- }
13
- ]
14
-
15
- const expectedOutput = `<meta charset="UTF-8" />
16
- <meta name="viewport" content="width=device-width, initial-scale=1" />
17
- <title>My Page</title>
18
- <script src="/script.js"></script>
19
- <link rel="stylesheet" href="/styles.css" />
20
- <meta name="description" content="My page description" />`
21
-
22
- console.log(renderHead(headItems))
23
-
24
- expect(renderHead(headItems)).toEqual(expectedOutput)
25
- })
26
-
27
- it('should throw an error if no title is provided', () => {
28
- const headItems: HeadItems[] = [
29
- {
30
- meta: [{ name: 'description', content: 'My page description' }]
31
- }
32
- ]
33
-
34
- expect(() => renderHead(headItems)).toThrowError('Missing title tag.')
35
- })
36
-
37
- it('should deduplicate meta tags', () => {
38
- const headItems: HeadItems[] = [
39
- {
40
- title: 'My Page',
41
- meta: [
42
- { name: 'description', content: 'My page description' },
43
- { name: 'description', content: 'Duplicate description' }
44
- ]
45
- }
46
- ]
47
-
48
- const expectedOutput = `<meta charset="UTF-8" />
49
- <meta name="viewport" content="width=device-width, initial-scale=1" />
50
- <title>My Page</title>
51
- <meta name="description" content="Duplicate description" />`
52
-
53
- expect(renderHead(headItems)).toEqual(expectedOutput)
54
- })
55
- })
56
-
57
- describe('renderAttrs', () => {
58
- it('should render boolean attributes correctly', () => {
59
- const item = { async: true, src: '/script.js' }
60
- expect(renderAttrs(item)).toEqual('async src="/script.js"')
61
- })
62
-
63
- it('should filter out invalid attributes', () => {
64
- const item = {
65
- innerHTML: '<p>Hello</p>',
66
- priority: 1,
67
- tagName: 'script',
68
- src: '/script.js'
69
- }
70
- expect(renderAttrs(item)).toEqual('src="/script.js"')
71
- })
72
- })