diff --git a/pony-frontend/src/App.test.js b/pony-frontend/src/App.test.js
index 32ab8e4..843d568 100644
--- a/pony-frontend/src/App.test.js
+++ b/pony-frontend/src/App.test.js
@@ -58,7 +58,7 @@ jest.mock('./components/SpeakButton', () => function SpeakButton() { return null
jest.mock('./components/VoiceButton', () => function VoiceButton() { return null; });
import React from 'react';
-import { render, screen, waitFor } from '@testing-library/react';
+import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';
import * as SFX from './SceneMusic';
@@ -127,12 +127,13 @@ function mockMultiFetch(responses) {
const findByText = (t) => screen.getByText(t);
// Clicking a pony type on the configurator's first step only selects the
-// type and advances to the body-color step; five more "Næste" clicks walk
-// through eyes/mane/tail/horn/wings to the final step, whose "Start eventyr"
-// button actually starts the game.
+// type and advances to the body-color step. How many "Næste" clicks it
+// takes to reach the end depends on the type — a Jordpony has neither
+// horn nor wings steps, so its wizard is shorter than an Alicorn's — so
+// just click "Næste" until "Start eventyr" appears instead of a fixed count.
async function pickPonyAndStartGame(name = 'Jordpony') {
await userEvent.click(screen.getByText(name));
- for (let i = 0; i < 5; i++) {
+ while (screen.queryByRole('button', { name: 'Næste trin' })) {
await userEvent.click(screen.getByRole('button', { name: 'Næste trin' }));
}
await userEvent.click(screen.getByRole('button', { name: 'Start eventyr' }));
@@ -332,6 +333,10 @@ test('shows four story choices instead of dice in a choice scene', async () => {
const choices = screen.getAllByRole('button', { name: /^Vælg / });
expect(choices).toHaveLength(4);
expect(choices[0].closest('.game-controls')).toHaveClass('game-controls-options');
+ act(() => window.dispatchEvent(new CustomEvent('pony-narration-status', {
+ detail: { status: 'preparing' },
+ })));
+ choices.forEach(choice => expect(choice).toBeEnabled());
expect(screen.queryByRole('button', { name: 'Kast terningerne' })).not.toBeInTheDocument();
});
diff --git a/pony-frontend/src/pages/PixelPonyConfiguratorPage.js b/pony-frontend/src/pages/PixelPonyConfiguratorPage.js
index ce64a9c..6fc6de4 100644
--- a/pony-frontend/src/pages/PixelPonyConfiguratorPage.js
+++ b/pony-frontend/src/pages/PixelPonyConfiguratorPage.js
@@ -1,10 +1,11 @@
/**
* Pixel Pony Configurator — build your pony in steps: type, body color,
- * eyes, mane, tail, horn, wings. Replaces the old plain pony-type select
- * screen; finishing the wizard starts the game with the chosen type.
+ * eyes, mane, tail, and (only if the chosen type actually has them) horn
+ * and/or wings. Replaces the old plain pony-type select screen; finishing
+ * the wizard starts the game with the chosen type.
*/
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import { motion } from 'framer-motion';
import SceneMusic from '../SceneMusic';
import VolumeControl from '../components/VolumeControl';
@@ -18,7 +19,6 @@ import {
} from '../pixelPony/spriteData';
import { loadAppearance, saveAppearance } from '../services/ponyAppearance';
-const STEPS = ['type', 'body', 'eyes', 'mane', 'tail', 'horn', 'wings'];
const STEP_TITLES = {
type: 'Vælg din Pony! 🐴',
body: 'Vælg krop-farve 🎨',
@@ -54,6 +54,18 @@ export default function PixelPonyConfiguratorPage({ ponies, onSelectType, volume
const [appearance, setAppearance] = useState(loadAppearance);
const [bobFrame, setBobFrame] = useState(false);
+ const selectedType = typeIdx !== null ? (PONY_TYPES[typeIdx] || PONY_TYPES[0]) : null;
+
+ // Horn/wings only appear as steps — and only exist at all — if the
+ // chosen pony type actually has them. A Jordpony has neither, so it
+ // never gets the chance to pick a horn or wings.
+ const steps = useMemo(() => {
+ const base = ['type', 'body', 'eyes', 'mane', 'tail'];
+ if (selectedType?.hasHorn) base.push('horn');
+ if (selectedType?.hasWings) base.push('wings');
+ return base;
+ }, [selectedType]);
+
useEffect(() => {
const id = window.setInterval(() => setBobFrame(b => !b), 500);
return () => window.clearInterval(id);
@@ -76,23 +88,25 @@ export default function PixelPonyConfiguratorPage({ ponies, onSelectType, volume
}
};
- const goNext = () => setStep(s => Math.min(s + 1, STEPS.length - 1));
+ const goNext = () => setStep(s => Math.min(s + 1, steps.length - 1));
const handleStart = () => {
saveAppearance(appearance);
onSelectType(typeIdx ?? 0);
};
- const stepName = STEPS[step];
- const isLastStep = step === STEPS.length - 1;
+ const stepName = steps[step];
+ const isLastStep = step === steps.length - 1;
const narration = {
type: 'Vælg din pony type. Tryk på den pony du vil være.',
body: 'Vælg en farve til din ponys krop.',
eyes: 'Vælg en farve til din ponys øjne.',
mane: 'Vælg en manke og en mankefarve til din pony.',
- tail: 'Vælg en hale og en halefarve til din pony.',
- horn: 'Vælg om din pony skal have horn, og vælg form og farve.',
- wings: 'Vælg om din pony skal have vinger, og vælg form og farve. Tryk på start eventyr når du er klar.',
+ tail: isLastStep
+ ? 'Vælg en hale og en halefarve til din pony. Tryk på start eventyr når du er klar.'
+ : 'Vælg en hale og en halefarve til din pony.',
+ horn: 'Vælg form og farve på hornet.',
+ wings: 'Vælg form og farve på vingerne. Tryk på start eventyr når du er klar.',
}[stepName];
return (
@@ -114,7 +128,7 @@ export default function PixelPonyConfiguratorPage({ ponies, onSelectType, volume
Trin {step + 1} af {STEPS.length}
+Trin {step + 1} af {steps.length}