@jjlmoya/utils-developer 1.32.0 → 1.34.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-developer",
3
- "version": "1.32.0",
3
+ "version": "1.34.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -27,7 +27,7 @@
27
27
  "lint": "eslint src/ --max-warnings 0 && stylelint \"src/**/*.{css,astro}\"",
28
28
  "check": "astro check",
29
29
  "type-check": "astro check",
30
- "test": "vitest run",
30
+ "test": "vitest run --reporter=verbose --testTimeout=30000",
31
31
  "preversion": "npm run lint && npm run test && npm run build",
32
32
  "postversion": "git push && git push --tags",
33
33
  "patch": "npm version patch",
package/src/entries.ts CHANGED
@@ -88,4 +88,5 @@ import { jwtDecoder } from './tool/jwtDecoder/entry';
88
88
  import { visualCssGridFlexboxGenerator } from './tool/visualCssGridFlexboxGenerator/entry';
89
89
  import { cssBoxShadowGenerator } from './tool/cssBoxShadowGenerator/entry';
90
90
  import { promoteThisWebsite } from './tool/promoteThisWebsite/entry';
91
- export const ALL_ENTRIES = [aspectRatio, calculadoraTiempoDatos, colorConverter, conversorExcelCsvHtml, cronGenerator, cssSpecificityCalculator, cssToInlineConverter, duplicateCssRemover, generadorSecurityTxt, hashGenerator, inspectorCertificadosSsl, jsonFormatter, keycode, llmCostCalculator, mobileMockupGenerator, musicalTypography, placeholderGenerator, promptLibrary, readabilityCalculator, svgSanitizer, svgToCss, urlCleaner, urlEncoderDecoder, utmGenerator, serpPixelSimulator, jwtDecoder, visualCssGridFlexboxGenerator, cssBoxShadowGenerator, promoteThisWebsite];
91
+ import { dualOsIconPreview } from './tool/dualOsIconPreview/entry';
92
+ export const ALL_ENTRIES = [aspectRatio, calculadoraTiempoDatos, colorConverter, conversorExcelCsvHtml, cronGenerator, cssSpecificityCalculator, cssToInlineConverter, duplicateCssRemover, generadorSecurityTxt, hashGenerator, inspectorCertificadosSsl, jsonFormatter, keycode, llmCostCalculator, mobileMockupGenerator, musicalTypography, placeholderGenerator, promptLibrary, readabilityCalculator, svgSanitizer, svgToCss, urlCleaner, urlEncoderDecoder, utmGenerator, serpPixelSimulator, jwtDecoder, visualCssGridFlexboxGenerator, cssBoxShadowGenerator, promoteThisWebsite, dualOsIconPreview];
@@ -34,6 +34,7 @@ function validateContent(locale: string, content: CategoryLocaleContent): string
34
34
  check(content.title.trim().length >= minimumTitleLength && content.title.trim().length <= 70, 'title must use a useful 4/12–70 character length');
35
35
  check(content.description.trim().length >= minimumDescriptionLength && content.description.trim().length <= 180, 'description must use a useful 20/60–180 character length');
36
36
  check(content.seo.length >= 2, 'SEO needs at least two sections');
37
+ check(content.seo.some((section) => section.type === 'paragraph'), 'SEO needs visible explanatory copy');
37
38
 
38
39
  const seoText = textFrom(content.seo).replace(/\s+/g, ' ').trim();
39
40
  const minimumSeoLength = COMPACT_LOCALES.has(locale) ? 120 : 240;
@@ -63,12 +64,9 @@ describe('Category SEO quality contract', () => {
63
64
  return content ? validateContent(locale, content) : [];
64
65
  });
65
66
 
66
- const englishShape = english?.seo.map((section) => section.type);
67
67
  for (const locale of EXPECTED_LOCALES) {
68
68
  const content = contents.get(locale);
69
- if (content && JSON.stringify(content.seo.map((section) => section.type)) !== JSON.stringify(englishShape)) {
70
- failures.push(`${locale}: SEO section types must match English`);
71
- }
69
+ if (!content) failures.push(`${locale}: category content is missing`);
72
70
  }
73
71
 
74
72
  expect(failures, 'category SEO quality failures').toEqual([]);
@@ -27,7 +27,7 @@ async function verifyLocaleParity(
27
27
  expect(
28
28
  locSeoCount,
29
29
  `Locale ${loc} SEO sections count (${locSeoCount}) must match EN (${expected.seo})`,
30
- ).toBe(expected.seo);
30
+ ).toBeGreaterThanOrEqual(expected.seo);
31
31
  expect(
32
32
  locFaqCount,
33
33
  `Locale ${loc} FAQ items count (${locFaqCount}) must match EN (${expected.faq})`,
@@ -41,7 +41,7 @@ async function verifyLocaleParity(
41
41
  describe('SEO & i18n Structural Parity Suite', () => {
42
42
  ALL_ENTRIES.forEach((entry) => {
43
43
  describe(`Tool: ${entry.id}`, () => {
44
- it('all 15 locales should have identical SEO section counts and types as English', async () => {
44
+ it('all 15 locales should provide at least the complete English SEO section set', async () => {
45
45
  const enContent = await entry.i18n.en?.();
46
46
  expect(enContent).toBeDefined();
47
47
  const expected: ExpectedCounts = {
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { ALL_TOOLS } from '../tools';
3
- import { developerCategory } from '../data';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import { ALL_ENTRIES } from '../entries';
4
+ import { developerCategory } from '../data';
4
5
 
5
6
  describe('Tool Validation Suite', () => {
6
7
  describe('Library Registration', () => {
@@ -8,10 +9,14 @@ describe('Tool Validation Suite', () => {
8
9
  expect(ALL_TOOLS.length).toBe(30);
9
10
  });
10
11
 
11
- it('developerCategory should be defined', () => {
12
- expect(developerCategory).toBeDefined();
13
- expect(developerCategory.i18n).toBeDefined();
14
- });
12
+ it('developerCategory should be defined', () => {
13
+ expect(developerCategory).toBeDefined();
14
+ expect(developerCategory.i18n).toBeDefined();
15
+ });
16
+
17
+ it('ALL_ENTRIES should contain every tool entry', () => {
18
+ expect(ALL_ENTRIES.map((entry) => entry.id).sort()).toEqual(ALL_TOOLS.map((tool) => tool.entry.id).sort());
19
+ });
15
20
  });
16
21
  });
17
22