fix(app): the dictionary adds each word once, says when it can't, and Look up tags hold
- A word tapped twice, or added while a level is being added, makes one card: adds run one at a time, and the word counts as in the studyset at once. The adder reads the studyset's cards once rather than for every word. - Adding that fails says so, and a level shows "Making the cards…" once its words have come. - Finding a studyset's dictionary tells finding, none and unreachable apart: the dictionary screen says there is none or offers Retry, and an instance unreachable at start is asked again once a sync goes through (Look up and From the dictionary appear then). - Look up's tags wait on the card by name and are made when it is saved: leaving the card at once no longer saves it untagged, a word replaced leaves no tag behind, and a new word replaces any level or word class on the card, pinned ones too. Look up marks words already made into cards of the studyset. - The tag picker's Create keeps the case typed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
50ca46f6b1
commit
49e10895f4
1 changed files with 516 additions and 107 deletions
|
|
@ -448,15 +448,16 @@ void main() {
|
|||
'',
|
||||
).copyWith(type: CardType.vocabulary, meanings: const ['a meaning']);
|
||||
|
||||
Future<Set<String>> namesOf(CardComposerState state) async {
|
||||
Future<Set<String>> namesOf(CardDraft draft) async {
|
||||
final tagging = await tags.read();
|
||||
return {
|
||||
for (final id in state.byId('new')!.tagIds) tagging.byId(id)!.name,
|
||||
for (final id in draft.tagIds) tagging.byId(id)!.name,
|
||||
...draft.newTagNames,
|
||||
};
|
||||
}
|
||||
|
||||
test("fills the card and gives it the word's tags, made where "
|
||||
'missing, and saved with it', () async {
|
||||
test("fills the card and gives it the word's tags: those that exist at "
|
||||
'once, the others made when it is saved', () async {
|
||||
final noun = await tags.create('Noun');
|
||||
start();
|
||||
await loaded();
|
||||
|
|
@ -470,11 +471,17 @@ void main() {
|
|||
'Noun',
|
||||
]),
|
||||
);
|
||||
final state = await until((s) => s.byId('new')?.tagIds.length == 3);
|
||||
// Once the tags are read, the one that exists is the card's own.
|
||||
final state = await until(
|
||||
(s) =>
|
||||
s.byId('new')?.content.front == text('食べる') &&
|
||||
s.byId('new')!.tagIds.isNotEmpty,
|
||||
);
|
||||
|
||||
expect(state.byId('new')!.content.front, text('食べる'));
|
||||
expect(await namesOf(state), {'JLPT-N5', 'Verb', 'Noun'});
|
||||
expect(state.byId('new')!.tagIds, contains(noun));
|
||||
final draft = state.byId('new')!;
|
||||
expect(draft.tagIds, {noun});
|
||||
expect(draft.newTagNames, ['JLPT-N5', 'Verb']);
|
||||
expect((await tags.read()).named('Verb'), isNull);
|
||||
|
||||
bloc.add(const CardComposerEventLeft('new'));
|
||||
final saved = await until(
|
||||
|
|
@ -485,36 +492,70 @@ void main() {
|
|||
{for (final tag in tagging.of(saved.byId('new')!.cardId!)) tag.name},
|
||||
{'JLPT-N5', 'Verb', 'Noun'},
|
||||
);
|
||||
expect(saved.byId('new')!.newTagNames, isEmpty);
|
||||
});
|
||||
|
||||
test('again swaps the tags the word before brought, and keeps the '
|
||||
"person's own", () async {
|
||||
test("again replaces the tags a word brings and keeps the person's "
|
||||
'own; the word replaced leaves no tag behind', () async {
|
||||
final mine = await tags.create('Food');
|
||||
final verb = await tags.create('Verb');
|
||||
start();
|
||||
await loaded();
|
||||
bloc
|
||||
..add(const CardComposerEventDraftAdded('new'))
|
||||
..add(CardComposerEventTagsChanged('new', {mine, verb}))
|
||||
..add(CardComposerEventTagsChanged('new', {mine}))
|
||||
..add(
|
||||
CardComposerEventLookedUp('new', word('食べる'), const [
|
||||
'JLPT-N5',
|
||||
'Verb',
|
||||
]),
|
||||
)
|
||||
..add(CardComposerEventLookedUp('new', word('寿司'), const ['Noun']));
|
||||
final state = await until(
|
||||
(s) =>
|
||||
s.byId('new')?.content.front == text('寿司') &&
|
||||
s.tagging.all.length == 2,
|
||||
);
|
||||
|
||||
expect(state.byId('new')!.tagIds, {mine});
|
||||
expect(await namesOf(state.byId('new')!), {'Food', 'Noun'});
|
||||
expect((await tags.read()).named('JLPT-N5'), isNull);
|
||||
expect(state.byId('new')!.tagIds, isNot(contains(verb)));
|
||||
});
|
||||
|
||||
test('with tags pinned, the next card keeps the tags but takes the '
|
||||
"next word's level and class", () async {
|
||||
final mine = await tags.create('Lesson 3');
|
||||
start();
|
||||
await loaded();
|
||||
bloc
|
||||
..add(const CardComposerEventDraftAdded('first'))
|
||||
..add(CardComposerEventTagsChanged('first', {mine}))
|
||||
..add(
|
||||
CardComposerEventLookedUp('first', word('寿司'), const [
|
||||
'JLPT-N5',
|
||||
'Noun',
|
||||
]),
|
||||
)
|
||||
..add(const CardComposerEventPinToggled(CardPin.tags))
|
||||
..add(const CardComposerEventDraftAdded('second', after: 'first'))
|
||||
..add(
|
||||
CardComposerEventLookedUp('second', word('走る'), const [
|
||||
'JLPT-N3',
|
||||
'Verb',
|
||||
]),
|
||||
);
|
||||
await until((s) => s.byId('new')?.tagIds.length == 3);
|
||||
final level = (await tags.read()).named('JLPT-N5')!.id;
|
||||
|
||||
bloc.add(
|
||||
CardComposerEventLookedUp('new', word('寿司'), const ['Noun']),
|
||||
);
|
||||
final swapped = await until(
|
||||
(s) => !(s.byId('new')?.tagIds.contains(level) ?? true),
|
||||
final state = await until(
|
||||
(s) =>
|
||||
s.byId('second')?.content.front == text('走る') &&
|
||||
s.tagging.all.isNotEmpty,
|
||||
);
|
||||
|
||||
expect(swapped.byId('new')!.content.front, text('寿司'));
|
||||
// Verb was the person's before the first word came, so it stays.
|
||||
expect(await namesOf(swapped), {'Food', 'Verb', 'Noun'});
|
||||
expect(await namesOf(state.byId('second')!), {
|
||||
'Lesson 3',
|
||||
'JLPT-N3',
|
||||
'Verb',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue