@jjlmoya/utils-nature 1.22.0 → 1.23.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-nature",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readdirSync, readFileSync, existsSync } from 'fs';
3
+ import { join, relative } from 'path';
4
+
5
+ function findBibliographyAstroFiles(dir: string): string[] {
6
+ const files: string[] = [];
7
+ if (!existsSync(dir)) return files;
8
+ const entries = readdirSync(dir, { withFileTypes: true });
9
+
10
+ for (const entry of entries) {
11
+ const fullPath = join(dir, entry.name);
12
+ if (entry.isDirectory()) {
13
+ files.push(...findBibliographyAstroFiles(fullPath));
14
+ } else if (entry.isFile() && entry.name === 'bibliography.astro') {
15
+ files.push(fullPath);
16
+ }
17
+ }
18
+
19
+ return files;
20
+ }
21
+
22
+ const toolDir = join(process.cwd(), 'src', 'tool');
23
+ const bibliographyFiles = findBibliographyAstroFiles(toolDir);
24
+
25
+ describe('Bibliography Component Wellformed Export', () => {
26
+ it('found tool bibliography.astro components', () => {
27
+ expect(bibliographyFiles.length).toBeGreaterThan(0);
28
+ });
29
+
30
+ bibliographyFiles.forEach((file) => {
31
+ const relativePath = relative(process.cwd(), file);
32
+
33
+ it(`${relativePath} should use SharedBibliography from @jjlmoya/utils-shared`, () => {
34
+ const content = readFileSync(file, 'utf-8');
35
+
36
+ const usesSharedComponent =
37
+ content.includes('@jjlmoya/utils-shared') &&
38
+ (content.includes('Bibliography') || content.includes('SharedBibliography'));
39
+
40
+ expect(
41
+ usesSharedComponent,
42
+ `File "${relativePath}" does not use the standard Bibliography component from @jjlmoya/utils-shared, resulting in unstyled or raw bibliography output.`,
43
+ ).toBe(true);
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readdirSync, readFileSync, existsSync } from 'fs';
3
+ import { join, relative } from 'path';
4
+
5
+ function findSeoAstroFiles(dir: string): string[] {
6
+ const files: string[] = [];
7
+ if (!existsSync(dir)) return files;
8
+ const entries = readdirSync(dir, { withFileTypes: true });
9
+
10
+ for (const entry of entries) {
11
+ const fullPath = join(dir, entry.name);
12
+ if (entry.isDirectory()) {
13
+ files.push(...findSeoAstroFiles(fullPath));
14
+ } else if (entry.isFile() && entry.name === 'seo.astro') {
15
+ files.push(fullPath);
16
+ }
17
+ }
18
+
19
+ return files;
20
+ }
21
+
22
+ const toolDir = join(process.cwd(), 'src', 'tool');
23
+ const seoFiles = findSeoAstroFiles(toolDir);
24
+
25
+ describe('SEO Component Wellformed Export', () => {
26
+ it('found tool seo.astro components', () => {
27
+ expect(seoFiles.length).toBeGreaterThan(0);
28
+ });
29
+
30
+ seoFiles.forEach((file) => {
31
+ const relativePath = relative(process.cwd(), file);
32
+
33
+ it(`${relativePath} should dynamically load SEO sections and use SEORenderer`, () => {
34
+ const content = readFileSync(file, 'utf-8');
35
+
36
+ const usesSeoRenderer =
37
+ content.includes('@jjlmoya/utils-shared') &&
38
+ (content.includes('SEORenderer') || content.includes('SEOArticle'));
39
+
40
+ const acquiresDynamicContent =
41
+ content.includes('.i18n') ||
42
+ content.includes('loader') ||
43
+ content.includes('await loader') ||
44
+ content.includes('.seo');
45
+
46
+ const isBrokenPattern =
47
+ /sections\s*=\s*\[\s*\]/.test(content) && !acquiresDynamicContent;
48
+
49
+ expect(
50
+ usesSeoRenderer,
51
+ `File "${relativePath}" does not import or use SEORenderer from @jjlmoya/utils-shared.`,
52
+ ).toBe(true);
53
+
54
+ expect(
55
+ isBrokenPattern,
56
+ `File "${relativePath}" relies on a static sections=[] prop default without fetching content from entry.i18n, resulting in empty SEO text on consumers.`,
57
+ ).toBe(false);
58
+
59
+ expect(
60
+ acquiresDynamicContent,
61
+ `File "${relativePath}" does not fetch dynamic i18n content for SEO rendering.`,
62
+ ).toBe(true);
63
+ });
64
+ });
65
+ });
@@ -1,18 +1,6 @@
1
1
  ---
2
- import type { BibliographyEntry } from '../../types';
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
3
  import { bibliography } from './bibliography';
4
-
5
- interface Props {
6
- locale?: string;
7
- }
8
-
9
- const { locale = 'en' } = Astro.props as Props;
10
- const entries = bibliography;
11
4
  ---
12
5
 
13
- <section class="compost-references" aria-labelledby="compost-references-title" data-locale={locale}>
14
- <h2 id="compost-references-title">References</h2>
15
- <ul>
16
- {entries.map((entry: BibliographyEntry) => <li><a href={entry.url} target="_blank" rel="noreferrer">{entry.name}</a></li>)}
17
- </ul>
18
- </section>
6
+ <SharedBibliography links={bibliography} />
@@ -1,23 +1,15 @@
1
1
  ---
2
- import type { SEOSection } from '../../types';
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
3
  import { compostBinVolumeRatioCalculator } from './entry';
4
+ import type { KnownLocale } from '../../types';
4
5
 
5
6
  interface Props {
6
- locale?: string;
7
+ locale?: KnownLocale;
7
8
  }
8
9
 
9
- const { locale = 'en' } = Astro.props as Props;
10
- const loader = compostBinVolumeRatioCalculator.i18n[locale as keyof typeof compostBinVolumeRatioCalculator.i18n] ?? compostBinVolumeRatioCalculator.i18n.en;
10
+ const { locale = 'es' } = Astro.props;
11
+ const loader = compostBinVolumeRatioCalculator.i18n[locale as KnownLocale] ?? compostBinVolumeRatioCalculator.i18n.es ?? compostBinVolumeRatioCalculator.i18n.en;
11
12
  const content = loader ? await loader() : null;
12
- const sections = (content?.seo ?? []) as SEOSection[];
13
13
  ---
14
14
 
15
- <div class="compost-seo" data-locale={locale}>
16
- {sections.map((section) => {
17
- if (section.type === 'title') return <h2>{section.text}</h2>;
18
- if (section.type === 'paragraph') return <p set:html={section.html} />;
19
- if (section.type === 'list') return <ul>{section.items.map((item) => <li set:html={item} />)}</ul>;
20
- if (section.type === 'tip') return <aside><h3>{section.title}</h3><div set:html={section.html} /></aside>;
21
- return null;
22
- })}
23
- </div>
15
+ {content?.seo && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}