Merge pull request #83 from kristianwind/opgavens-tekst-kan-laeses
CI / OpenAPI is valid (push) Successful in 11s
CI / End-to-end (push) Successful in 10m47s
CI / Docker image builds (push) Successful in 2m1s
Release / Build and publish image (push) Failing after 6s
CI / Go (push) Failing after 14m57s

fix(opgave): teksten kan læses færdig, også på en telefon
This commit is contained in:
Kristian Wind
2026-09-15 20:22:38 +02:00
committed by GitHub
2 changed files with 122 additions and 2 deletions
+66
View File
@@ -4755,3 +4755,69 @@ test('et indsat billede kan åbnes stort og kopieres', async ({ page, context })
expect(trouble).toEqual([]);
});
/**
* En opgaves tekst kan læses færdig, også på en telefon.
*
* Rapporteret fra en telefon, med et billede af en beskrivelse, der manglede sit
* første bogstav på hver linje: "hermostat RU0910301440" og "atteries". Det er
* ikke manglende tegn — det er et felt, der er rullet til højre. En beskrivelse er
* tit et klippet stykke mail med en URL i, et <textarea> ombryder ved mellemrum,
* og en URL har ingen.
*
* Og titlen var et <input>, som ikke kan ombryde overhovedet. "tado°: Your Smart
* Thermostat needs new batteries" er en almindelig opgavetitel, og halvdelen af
* den stod uden for kanten.
*/
test('en opgaves tekst kan læses færdig på en telefon', async ({ page }) => {
const trouble = watchForTrouble(page);
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/');
const langTitel = 'tado°: Your Smart Thermostat needs new batteries';
// "i dag" for at den lander på siden, prøven står på — parseren klipper det ud
// af titlen igen, så den hedder præcis det, den skal.
await page.getByLabel('Ny opgave').fill(`${langTitel} i dag`);
await page.getByLabel('Ny opgave').press('Enter');
await page.getByText('tado°', { exact: false }).first().click();
const titel = page.getByLabel('Opgavens tekst');
await expect(titel).toBeVisible();
await expect(titel).toHaveValue(langTitel);
// Beskrivelsen: en URL uden et eneste mellemrum at bryde ved.
const url = 'https://link.tado.com/emails/8/ttid/1-a37f2c9b4e6d8a1f0c3b5e7d9a2f4c6b8e0d1a3f5c7b9e2d4a6f8c0b3e5d7a9f';
const beskrivelse = page.getByLabel('Beskrivelse');
await beskrivelse.fill(`Thermostat RU0910301440\nBatteries\n${url}`);
await beskrivelse.blur();
// Målt frem for beskrevet: et felt, der ruller vandret, har en scrollWidth
// større end sin clientWidth. Det var dét, der klippede venstre kant.
//
// Felterne måles gennem deres egne lokatorer og ikke med en forespørgsel på
// aria-label: beskrivelsen har et <label for>, ikke et aria-label, så en
// forespørgsel fandt ingenting og ville have bestået tomt.
const spild = [];
for (const [navn, felt] of [
['Opgavens tekst', titel],
['Beskrivelse', beskrivelse]
]) {
const m = await felt.evaluate((el) => ({ ind: el.scrollWidth, ude: el.clientWidth }));
if (m.ind - m.ude > 1) spild.push(`${navn}: ${m.ind} > ${m.ude}`);
}
expect(spild).toEqual([]);
// Beskrivelsen løber ikke over i Chromium, heller ikke uden reglen — det var
// Safari, den blev set i, og det er en anden motor. Prøven måler derfor reglen
// selv frem for dens virkning. Det er svagere, og det står her, så den næste
// ved det: den vogter mod at reglen bliver slettet, ikke mod at den er forkert.
await expect(beskrivelse).toHaveCSS('overflow-wrap', 'break-word');
// Og titlen er blevet høj nok til at rumme sig selv frem for at klippe.
const linjer = await titel.evaluate((el) => ({
hoejde: el.clientHeight,
linje: parseFloat(getComputedStyle(el).lineHeight)
}));
expect(linjer.hoejde).toBeGreaterThan(linjer.linje * 1.5);
expect(trouble).toEqual([]);
});
+56 -2
View File
@@ -171,6 +171,32 @@
* took is said out loud, because a title that edits itself without a word is
* worse than one that does nothing.
*/
/**
* Lader et felt være lige så højt som sin tekst.
*
* `height: auto` først, og så `scrollHeight`: uden nulstillingen kan feltet kun
* vokse, for scrollHeight på et felt, der allerede er for højt, er dets egen
* højde.
*/
function grow(node) {
const fit = () => {
node.style.height = 'auto';
node.style.height = `${node.scrollHeight}px`;
};
fit();
node.addEventListener('input', fit);
// Bredden skifter, når ruden gør — og en tekst, der fyldte to linjer, fylder
// tre på en smallere skærm uden at der er rørt ved den.
const watch = new ResizeObserver(fit);
watch.observe(node);
return {
destroy() {
node.removeEventListener('input', fit);
watch.disconnect();
}
};
}
async function saveContent() {
if (!task) return;
const trimmed = content.trim();
@@ -418,13 +444,27 @@
<div class="scroll">
<div class="field">
<input
<!-- Et felt frem for en linje. En opgavetitel er tit længere end en telefon
er bred — "tado°: Your Smart Thermostat needs new batteries" er en
almindelig en — og et <input> kan ikke ombryde: det ruller, og så står
halvdelen af overskriften uden for kanten. -->
<textarea
class="title"
rows="1"
use:focusOnMount
use:grow
bind:value={content}
onblur={saveContent}
onkeydown={(e) => {
// Retur gemmer frem for at lave en ny linje. En titel er én linje,
// uanset hvor mange den fylder på skærmen.
if (e.key === 'Enter') {
e.preventDefault();
e.currentTarget.blur();
}
}}
aria-label={t('detail.text')}
/>
></textarea>
</div>
<div class="field">
@@ -894,6 +934,16 @@
resize: vertical;
font-family: inherit;
line-height: 1.5;
/* En beskrivelse er tit et klippet stykke mail med en URL i. Et <textarea>
ombryder ved mellemrum, og en URL har ingen — så feltet rullede vandret,
og på en telefon stod teksten forskudt med venstre kant klippet af. Det
ser ikke ud som rulning; det ser ud som manglende bogstaver.
`break-word` og ikke `anywhere`: den første bryder kun et ord, der ikke kan
stå på en linje alene, mens den anden bryder ved først givne lejlighed og
delte "GarageRisteriet" midt i en stavelse. Se h1 på projektets side — den
lektion er betalt én gang. */
overflow-wrap: break-word;
}
/* The title is the one field that should not look like a field until you are in
@@ -905,6 +955,10 @@
background: transparent;
border-color: transparent;
padding-left: 0;
/* Højden sættes af `grow`, så håndtaget ville love noget, det ikke kan
holde: trækker man i det, retter det næste bogstav det tilbage. */
resize: none;
overflow: hidden;
}
.title:hover {