Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions js/project-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,16 @@ const limitProjectsForPage = (projects, pathname) => {
};

/**
* Prepares projects for display (sort + limit)
* Prepares projects for display (drop hidden + sort + limit)
* Pure function
*
*
* @param {Array<Object>} projects - Raw projects array
* @param {string} pathname - Window location pathname
* @returns {Array<Object>} - Prepared projects
*/
const prepareProjects = (projects, pathname) => {
const sorted = sortProjects(projects);
const visible = projects.filter(project => project.hidden !== true);
const sorted = sortProjects(visible);
return limitProjectsForPage(sorted, pathname);
};

Expand Down
1 change: 1 addition & 0 deletions projects/projects-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"liveUrl": "https://www.emilysueanderson.com/io-puzzle-2025-solutions",
"blogPostUrl": "/blog/solving-io-puzzles-2025/",
"featured": false,
"hidden": true,
Comment thread
emscape marked this conversation as resolved.
Resolved
"completionDate": "2025-03"
},
{
Expand Down
29 changes: 21 additions & 8 deletions scripts/index-all-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ function getBlogPostUrls() {
}
}

/**
* Map project entries to their URLs, skipping hidden ones (they are not
* listed on the site, matching js/project-loader.js)
*/
function projectUrls(projects) {
return projects.filter(project => project.hidden !== true).map(project => {
// Projects use 'id' not 'slug'
return `${SITE_BASE_URL}/projects/${project.id}/`;
});
}

/**
* Get all project URLs
*/
Expand All @@ -74,10 +85,7 @@ function getProjectUrls() {
}

const projectsData = JSON.parse(fs.readFileSync(PATHS.projectsData, 'utf8'));
return (projectsData.projects || []).map(project => {
// Projects use 'id' not 'slug'
return `${SITE_BASE_URL}/projects/${project.id}/`;
});
return projectUrls(projectsData.projects || []);
} catch (error) {
console.error('Error reading projects data:', error.message);
return [];
Expand Down Expand Up @@ -251,7 +259,12 @@ async function main() {
}
}

main().catch(error => {
console.error('Fatal error:', error.message);
process.exit(1);
});
module.exports = { projectUrls };

// Only submit when run directly, not when require()'d from a test.
if (require.main === module) {
main().catch(error => {
console.error('Fatal error:', error.message);
process.exit(1);
});
}
11 changes: 0 additions & 11 deletions sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -256,17 +256,6 @@
</image:image>
</url>

<url>
<loc>https://helloemily.dev/projects/io-puzzle-2025/</loc>
<lastmod>2026-01-13</lastmod>
<changefreq>never</changefreq>
<priority>0.8</priority>
<image:image>
<image:loc>https://helloemily.dev/projects/io-puzzle-2025/io-puzzle-1.jpeg</image:loc>
<image:title>I/O Puzzle 2025 Solutions</image:title>
</image:image>
</url>

<url>
<loc>https://helloemily.dev/projects/sean-cameron-website/</loc>
<lastmod>2026-01-13</lastmod>
Expand Down
31 changes: 31 additions & 0 deletions tests/index-all-content.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Tests for the search indexing script's project URLs
*/

import { describe, it, expect } from 'vitest';
import { createRequire } from 'module';
import { readFileSync } from 'fs';

const require = createRequire(import.meta.url);
const { projectUrls } = require('../scripts/index-all-content.js');

describe('index-all-content projectUrls', () => {
it('maps visible projects to their project URLs and skips hidden ones', () => {
const projects = [
{ id: 'shown' },
{ id: 'gone', hidden: true },
{ id: 'also-shown', hidden: false },
];

expect(projectUrls(projects)).toEqual([
'https://helloemily.dev/projects/shown/',
'https://helloemily.dev/projects/also-shown/',
]);
});

it('does not submit the hidden io-puzzle-2025 project from projects-data.json', () => {
const { projects } = JSON.parse(readFileSync('projects/projects-data.json', 'utf8'));

expect(projectUrls(projects)).not.toContain('https://helloemily.dev/projects/io-puzzle-2025/');
});
});
67 changes: 67 additions & 0 deletions tests/project-loader.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Tests for the project loader's display preparation
*/

import { describe, it, expect } from 'vitest';
import { readFileSync } from 'fs';
import { prepareProjects } from '../js/project-loader.js';

const project = (id, overrides = {}) => ({
id,
featured: false,
completionDate: '2025-01',
...overrides,
});

describe('Project Loader', () => {
describe('prepareProjects', () => {
it('omits hidden projects on the projects page', () => {
const projects = [
project('shown-a', { completionDate: '2025-03' }),
project('hidden', { hidden: true, featured: true }),
project('shown-b', { completionDate: '2025-01' }),
];

const ids = prepareProjects(projects, '/pages/projects.html').map((p) => p.id);

expect(ids).toEqual(['shown-a', 'shown-b']);
});

it('fills all three home page slots from visible projects when one is hidden', () => {
const projects = [
project('hidden', { hidden: true, featured: true, completionDate: '2025-06' }),
project('p1', { completionDate: '2025-05' }),
project('p2', { completionDate: '2025-04' }),
project('p3', { completionDate: '2025-03' }),
project('p4', { completionDate: '2025-02' }),
];

const ids = prepareProjects(projects, '/').map((p) => p.id);

expect(ids).toEqual(['p1', 'p2', 'p3']);
});

it('keeps projects with hidden set to false or absent', () => {
const projects = [
project('explicit', { hidden: false, completionDate: '2025-02' }),
project('absent', { completionDate: '2025-01' }),
];

const ids = prepareProjects(projects, '/pages/projects.html').map((p) => p.id);

expect(ids).toEqual(['explicit', 'absent']);
});
});

describe('projects-data.json', () => {
const { projects } = JSON.parse(readFileSync('projects/projects-data.json', 'utf8'));

it('does not display any project linking to emilysueanderson.com', () => {
const displayed = prepareProjects(projects, '/pages/projects.html');

const offending = displayed.filter((p) => /emilysueanderson\.com/i.test(JSON.stringify(p)));

expect(offending.map((p) => p.id)).toEqual([]);
});
});
});
Loading