@jjlmoya/utils-sports 1.1.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.
Files changed (79) hide show
  1. package/package.json +62 -0
  2. package/src/category/i18n/en.ts +108 -0
  3. package/src/category/i18n/es.ts +108 -0
  4. package/src/category/i18n/fr.ts +95 -0
  5. package/src/category/index.ts +21 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +55 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/gymTracker/bibliography.astro +15 -0
  25. package/src/tool/gymTracker/component.astro +835 -0
  26. package/src/tool/gymTracker/exercises.ts +28 -0
  27. package/src/tool/gymTracker/i18n/en.ts +225 -0
  28. package/src/tool/gymTracker/i18n/es.ts +225 -0
  29. package/src/tool/gymTracker/i18n/fr.ts +225 -0
  30. package/src/tool/gymTracker/index.ts +34 -0
  31. package/src/tool/gymTracker/logic.ts +169 -0
  32. package/src/tool/gymTracker/seo.astro +15 -0
  33. package/src/tool/gymTracker/storage.ts +43 -0
  34. package/src/tool/gymTracker/timer.ts +126 -0
  35. package/src/tool/gymTracker/types.ts +11 -0
  36. package/src/tool/gymTracker/ui-utils.ts +59 -0
  37. package/src/tool/gymTracker/ui.ts +27 -0
  38. package/src/tool/reactionTester/bibliography.astro +2 -0
  39. package/src/tool/reactionTester/component.astro +1074 -0
  40. package/src/tool/reactionTester/i18n/en.ts +144 -0
  41. package/src/tool/reactionTester/i18n/es.ts +144 -0
  42. package/src/tool/reactionTester/i18n/fr.ts +144 -0
  43. package/src/tool/reactionTester/index.ts +34 -0
  44. package/src/tool/reactionTester/seo.astro +12 -0
  45. package/src/tool/reactionTester/ui.ts +43 -0
  46. package/src/tool/scoreKeeper/bibliography.astro +14 -0
  47. package/src/tool/scoreKeeper/component.astro +858 -0
  48. package/src/tool/scoreKeeper/i18n/en.ts +207 -0
  49. package/src/tool/scoreKeeper/i18n/es.ts +207 -0
  50. package/src/tool/scoreKeeper/i18n/fr.ts +207 -0
  51. package/src/tool/scoreKeeper/index.ts +35 -0
  52. package/src/tool/scoreKeeper/logic.ts +275 -0
  53. package/src/tool/scoreKeeper/seo.astro +15 -0
  54. package/src/tool/scoreKeeper/sports.ts +70 -0
  55. package/src/tool/scoreKeeper/ui.ts +19 -0
  56. package/src/tool/tournamentBracket/bibliography.astro +10 -0
  57. package/src/tool/tournamentBracket/component.astro +1092 -0
  58. package/src/tool/tournamentBracket/i18n/en.ts +160 -0
  59. package/src/tool/tournamentBracket/i18n/es.ts +178 -0
  60. package/src/tool/tournamentBracket/i18n/fr.ts +160 -0
  61. package/src/tool/tournamentBracket/index.ts +34 -0
  62. package/src/tool/tournamentBracket/logic/active.controller.ts +106 -0
  63. package/src/tool/tournamentBracket/logic/generator.ts +71 -0
  64. package/src/tool/tournamentBracket/logic/manager.ts +165 -0
  65. package/src/tool/tournamentBracket/logic/setup.controller.ts +84 -0
  66. package/src/tool/tournamentBracket/logic/sharing.ts +81 -0
  67. package/src/tool/tournamentBracket/logic/storage.ts +56 -0
  68. package/src/tool/tournamentBracket/models.ts +34 -0
  69. package/src/tool/tournamentBracket/seo.astro +12 -0
  70. package/src/tool/tournamentBracket/tournament.controller.ts +65 -0
  71. package/src/tool/tournamentBracket/tournament.renderer.ts +45 -0
  72. package/src/tool/tournamentBracket/ui/bracket-desktop.ts +143 -0
  73. package/src/tool/tournamentBracket/ui/bracket-mobile.ts +82 -0
  74. package/src/tool/tournamentBracket/ui/mediator.ts +96 -0
  75. package/src/tool/tournamentBracket/ui/navigator.ts +84 -0
  76. package/src/tool/tournamentBracket/ui/setup.ts +120 -0
  77. package/src/tool/tournamentBracket/ui.ts +42 -0
  78. package/src/tools.ts +13 -0
  79. package/src/types.ts +72 -0
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readdirSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ const EXCLUDED_DIRS = ['node_modules', 'pages', 'layouts'];
6
+
7
+ function findAstroFiles(dir: string): string[] {
8
+ const files: string[] = [];
9
+ const entries = readdirSync(dir, { withFileTypes: true });
10
+
11
+ for (const entry of entries) {
12
+ const fullPath = join(dir, entry.name);
13
+ if (entry.isDirectory() && !EXCLUDED_DIRS.includes(entry.name)) {
14
+ files.push(...findAstroFiles(fullPath));
15
+ } else if (entry.isFile() && entry.name.endsWith('.astro')) {
16
+ files.push(fullPath);
17
+ }
18
+ }
19
+
20
+ return files;
21
+ }
22
+
23
+ function hasH1(content: string): boolean {
24
+ return /<h1[\s>]/i.test(content);
25
+ }
26
+
27
+ const srcDir = join(process.cwd(), 'src');
28
+ const astroFiles = findAstroFiles(srcDir);
29
+
30
+ describe('No H1 in Components', () => {
31
+ if (astroFiles.length === 0) {
32
+ it('no astro components found', () => {
33
+ expect(true).toBe(true);
34
+ });
35
+ }
36
+
37
+ astroFiles.forEach((file) => {
38
+ const relativePath = file.replace(process.cwd(), '');
39
+ it(`${relativePath} should not contain <h1>`, () => {
40
+ const content = readFileSync(file, 'utf-8');
41
+ expect(
42
+ hasH1(content),
43
+ `File "${relativePath}" contains a <h1> element. Use <h2> or lower inside components — h1 belongs to the page layout.`,
44
+ ).toBe(false);
45
+ });
46
+ });
47
+ });
48
+
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import type { ToolLocaleContent } from '../types';
4
+
5
+ describe('Schemas Fulfillment Validation', () => {
6
+ ALL_TOOLS.forEach((tool) => {
7
+ describe(`Tool: ${tool.entry.id}`, () => {
8
+ Object.keys(tool.entry.i18n).forEach((locale) => {
9
+ it(`Locale: ${locale} should have faqSchema, appSchema and howToSchema`, async () => {
10
+ const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
11
+ if (!loader) return;
12
+ const content = (await loader()) as ToolLocaleContent;
13
+
14
+ const schemaTypes = content.schemas.map((s: any) => s['@type']);
15
+
16
+ expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing FAQPage schema`).toContain('FAQPage');
17
+ expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing SoftwareApplication schema`).toContain('SoftwareApplication');
18
+ expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing HowTo schema`).toContain('HowTo');
19
+ });
20
+ });
21
+ });
22
+ });
23
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as DATA from '../data';
3
+
4
+ const ENTRIES = [
5
+ { id: 'sportsCategory', i18n: DATA.sportsCategory.i18n },
6
+ ];
7
+
8
+ describe('SEO Content Length Validation', () => {
9
+ ENTRIES.forEach((entry) => {
10
+ describe(`Tool: ${entry.id}`, () => {
11
+ Object.keys(entry.i18n).forEach((locale) => {
12
+ it(`${locale}: SEO section should exist`, async () => {
13
+ const loader = (entry.i18n as Record<string, () => Promise<{ seo?: unknown[] }>>)[locale];
14
+ const content = await loader();
15
+ if (!content.seo) return;
16
+ expect(Array.isArray(content.seo)).toBe(true);
17
+ });
18
+ });
19
+ });
20
+ });
21
+ });
22
+
@@ -0,0 +1,55 @@
1
+ import { describe, it } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ function getFiles(dir: string, ext: string[]): string[] {
6
+ const results: string[] = [];
7
+ if (!fs.existsSync(dir)) return results;
8
+ const list = fs.readdirSync(dir);
9
+ for (const file of list) {
10
+ const fullPath = path.join(dir, file);
11
+ const stat = fs.statSync(fullPath);
12
+ if (stat && stat.isDirectory()) {
13
+ results.push(...getFiles(fullPath, ext));
14
+ } else if (ext.some((e) => file.endsWith(e))) {
15
+ results.push(fullPath);
16
+ }
17
+ }
18
+ return results;
19
+ }
20
+
21
+ const SRC_DIR = path.join(process.cwd(), 'src');
22
+
23
+ describe('Project Titles - Separator Validation', () => {
24
+ const files = [
25
+ ...getFiles(path.join(SRC_DIR, 'tool'), ['.ts']),
26
+ ...getFiles(path.join(SRC_DIR, 'category'), ['.ts']),
27
+ ].filter(f => f.includes('i18n'));
28
+
29
+ it.each(files)('Verify that titles in %s do not contain | or -', (filePath) => {
30
+ const content = fs.readFileSync(filePath, 'utf-8');
31
+ const relativePath = path.relative(process.cwd(), filePath);
32
+
33
+ const titlePatterns = [
34
+ /const\s+title\s*=\s*['"]([^'"]+)['"]/g,
35
+ /title\s*:\s*['"]([^'"]+)['"]/g,
36
+ ];
37
+
38
+ const findings: string[] = [];
39
+
40
+ for (const pattern of titlePatterns) {
41
+ let match;
42
+ while ((match = pattern.exec(content)) !== null) {
43
+ const title = match[1];
44
+ if (title.includes('|') || title.includes('-')) {
45
+ findings.push(title);
46
+ }
47
+ }
48
+ }
49
+
50
+ if (findings.length > 0) {
51
+ const list = findings.map((f) => ` - "${f}"`).join('\n');
52
+ throw new Error(`Forbidden separators (| or -) found in titles in ${relativePath}:\n${list}`);
53
+ }
54
+ });
55
+ });
@@ -0,0 +1,17 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import { sportsCategory } from '../data';
4
+
5
+ describe('Tool Validation Suite', () => {
6
+ describe('Library Registration', () => {
7
+ it('should have 4 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(4);
9
+ });
10
+
11
+ it('sportsCategory should be defined', () => {
12
+ expect(sportsCategory).toBeDefined();
13
+ expect(sportsCategory.i18n).toBeDefined();
14
+ });
15
+ });
16
+ });
17
+
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { Bibliography } from '@jjlmoya/utils-shared';
3
+ import { gymTracker } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await gymTracker.i18n[locale]?.();
12
+ if (!content) return;
13
+ ---
14
+
15
+ <Bibliography links={content.bibliography} title={content.bibliographyTitle} />