@jjlmoya/utils-diy 1.27.0 → 1.28.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-diy",
3
- "version": "1.27.0",
3
+ "version": "1.28.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,14 +1,15 @@
1
1
  ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { drywallCalculatorEntry } from './entry';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const loader = drywallCalculatorEntry.i18n[locale as KnownLocale] ?? drywallCalculatorEntry.i18n.es ?? drywallCalculatorEntry.i18n.en;
12
+ const content = loader ? await loader() : null;
2
13
  ---
3
14
 
4
- <div class="drywall-seo-content">
5
- <p>
6
- Planning a drywall partition wall or ceiling lining requires precise calculations of light gauge steel framing members, plasterboard sheets, and jointing materials. This calculator provides complete material takeoffs based on standard architectural specifications for dry construction systems.
7
- </p>
8
- <h3>Key Drywall Installation Principles</h3>
9
- <ul>
10
- <li><strong>Stud Spacing:</strong> Standard non-load-bearing interior partitions use 60 cm stud centers for speed and efficiency. Damp locations or tiled walls require 40 cm centers to prevent structural deflection.</li>
11
- <li><strong>Joint Staggering:</strong> Plasterboard sheets must be staggered horizontally by at least 40 cm between courses to prevent continuous vertical stress seams.</li>
12
- <li><strong>Fastener Density:</strong> Board perimeter screws are placed every 25 cm, while interior field screws are spaced every 30 cm along studs.</li>
13
- </ul>
14
- </div>
15
+ {content?.seo && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -1,11 +1,15 @@
1
1
  ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { excavationVolumeCalculator } from './entry';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const loader = excavationVolumeCalculator.i18n[locale as KnownLocale] ?? excavationVolumeCalculator.i18n.es ?? excavationVolumeCalculator.i18n.en;
12
+ const content = loader ? await loader() : null;
2
13
  ---
3
14
 
4
- <div class="ev-seo-content">
5
- <p>Excavation planning has two different questions. How much ground is removed from the cut, and how much room will that disturbed material occupy when it is loaded or stored? This calculator keeps those answers separate.</p>
6
- <h2>Bank volume is the geometry of the cut</h2>
7
- <p>Multiply length by width by depth to get the bank volume in undisturbed ground. Use this figure when describing the excavation, comparing it with a foundation layout, or checking the amount of material that must leave the cut.</p>
8
- <h2>Loose volume is the handling allowance</h2>
9
- <p>Digging breaks the original packing arrangement. The soil then occupies more space, so the tool applies a visible expansion band to the bank volume. Use the lower and upper loose figures when choosing skips, truck capacity, stockpile space, or a temporary staging area.</p>
10
- <p>The band is a planning assumption, not a laboratory result. Moisture, density, clay mineralogy, groundwater, excavation method, and later compaction can move the real volume. A site measurement is still needed for a foundation, retaining structure, disposal record, contaminated ground, or expansive clay.</p>
11
- </div>
15
+ {content?.seo && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}