fix: stabilize portfolio scrolling and loading
Remove skeleton swaps and deferred layout estimates, restore reveal state, and use native proximity scrolling for project cards. Also make the Docker build npm registry configurable.
This commit is contained in:
87
tests/scroll-and-loading.test.mjs
Normal file
87
tests/scroll-and-loading.test.mjs
Normal file
@@ -0,0 +1,87 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { access, readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const removedSkeletonFiles = [
|
||||
'src/app/(en)/loading.tsx',
|
||||
'src/app/(fa)/fa/loading.tsx',
|
||||
'src/components/ImageWithSkeleton.tsx',
|
||||
'src/components/ImageWithSkeleton.module.css',
|
||||
'src/components/skeletons/PortfolioSkeleton.tsx',
|
||||
'src/components/skeletons/PortfolioSkeleton.module.css',
|
||||
];
|
||||
|
||||
test('portfolio rendering no longer uses skeleton loading layers', async () => {
|
||||
await Promise.all(
|
||||
removedSkeletonFiles.map(async (file) => {
|
||||
await assert.rejects(access(file), `${file} should be removed`);
|
||||
}),
|
||||
);
|
||||
|
||||
const [page, scene, sceneStyles, sourceFiles] = await Promise.all([
|
||||
readFile('src/app/PortfolioPage.tsx', 'utf8'),
|
||||
readFile('src/components/scenes/DeferredAboutRiverScene.tsx', 'utf8'),
|
||||
readFile('src/components/scenes/Scenes.module.css', 'utf8'),
|
||||
Promise.all([
|
||||
'src/components/HeroSection.tsx',
|
||||
'src/components/ProjectThumbnail.tsx',
|
||||
'src/components/ExperienceSection.tsx',
|
||||
'src/components/SkillsSection.tsx',
|
||||
'src/components/EducationSection.tsx',
|
||||
'src/components/MilestonesSection.tsx',
|
||||
].map((file) => readFile(file, 'utf8'))).then((files) => files.join('\n')),
|
||||
]);
|
||||
|
||||
assert.doesNotMatch(page, /Suspense|Skeleton/);
|
||||
assert.doesNotMatch(sourceFiles, /ImageWithSkeleton|skeletonShape/);
|
||||
assert.doesNotMatch(scene, /isReady|onReady|sceneSkeleton|aria-busy/);
|
||||
assert.doesNotMatch(sceneStyles, /sceneSkeleton|sceneSkeletonShimmer/);
|
||||
});
|
||||
|
||||
test('deferred section sizing cannot replace real layout while scrolling', async () => {
|
||||
const [globals, sections] = await Promise.all([
|
||||
readFile('src/styles/globals.css', 'utf8'),
|
||||
Promise.all([
|
||||
'src/components/ProjectsSection.tsx',
|
||||
'src/components/ExperienceSection.tsx',
|
||||
'src/components/SkillsSection.tsx',
|
||||
'src/components/EducationSection.tsx',
|
||||
'src/components/MilestonesSection.tsx',
|
||||
'src/components/FooterSection.tsx',
|
||||
].map((file) => readFile(file, 'utf8'))).then((files) => files.join('\n')),
|
||||
]);
|
||||
|
||||
assert.doesNotMatch(globals, /content-visibility|contain-intrinsic-size/);
|
||||
assert.doesNotMatch(sections, /data-deferred-render/);
|
||||
});
|
||||
|
||||
test('project carousel uses native momentum with soft proximity snapping', async () => {
|
||||
const [component, styles] = await Promise.all([
|
||||
readFile('src/components/ProjectsSection.tsx', 'utf8'),
|
||||
readFile('src/components/ProjectsSection.module.css', 'utf8'),
|
||||
]);
|
||||
|
||||
assert.match(styles, /scroll-snap-type:\s*x proximity/);
|
||||
assert.match(styles, /touch-action:\s*pan-x pan-y/);
|
||||
assert.doesNotMatch(styles, /scroll-snap-type:\s*x mandatory/);
|
||||
assert.doesNotMatch(styles, /scroll-snap-stop:\s*always/);
|
||||
assert.match(component, /event\.pointerType !== 'mouse'/);
|
||||
assert.match(
|
||||
component,
|
||||
/cancelAnimationFrame\(scrollFrameRef\.current\);\s*scrollFrameRef\.current = 0;/,
|
||||
);
|
||||
assert.doesNotMatch(component, /targetIndex|velocity fling|Math\.(?:ceil|floor|round)\(currentScroll \/ step\)/);
|
||||
});
|
||||
|
||||
test('motion state is initialized for dependent section animations', async () => {
|
||||
const [controller, footer] = await Promise.all([
|
||||
readFile('src/components/MotionController.tsx', 'utf8'),
|
||||
readFile('src/components/IsometricFooter/IsometricFooter.tsx', 'utf8'),
|
||||
]);
|
||||
|
||||
assert.match(controller, /document\.documentElement\.dataset\.motionReady = 'true'/);
|
||||
assert.match(controller, /element\.dataset\.visible = 'true'/);
|
||||
assert.match(footer, /className=\{styles\.headerTitle\} data-reveal="soft"/);
|
||||
assert.match(footer, /className=\{styles\.headerSubtitle\} data-reveal="soft"/);
|
||||
assert.match(footer, /className=\{styles\.emailWrapper\} data-reveal="scale"/);
|
||||
});
|
||||
Reference in New Issue
Block a user