feat(onboarding): Transactions card suggests Add as keyword, Budget card names leaves and the Annual column (#430) #442
11 changed files with 149 additions and 37 deletions
|
|
@ -11,6 +11,7 @@
|
|||
- Le champ « Catégorie parente » du formulaire de catégorie et le champ « Catégorie » du dialogue « Nouveau mot-clé » (clic droit sur une transaction) acceptent désormais la saisie, comme la cellule de catégorie de la page Transactions : tapez quelques lettres pour filtrer la liste, qui montre la hiérarchie avec son indentation et les noms des catégories standard dans votre langue. La liste des parents ne propose plus la catégorie en cours d'édition, ses propres sous-catégories, ni un parent qui créerait un quatrième niveau ; un parent déjà enregistré reste affiché. Le dialogue de mot-clé propose les mêmes catégories que la page Transactions (actives et autorisées à la saisie) et ne garde la catégorie pré-remplie que si elle en fait partie ; ses deux champs sont placés au-dessus de la liste défilante des correspondances, pour que la liste de catégories ouverte ne soit jamais coupée (#423).
|
||||
- Une seule copie est gardée par profil dans le dossier `migration-backups` : la copie prise avant une mise à jour de la base est désormais refaite à chaque mise à jour, et une fois la mise à jour réussie, les copies plus anciennes de ce profil sont supprimées. Une copie laissée par une mise à jour interrompue n'est plus réutilisée telle quelle, puisqu'elle précédait ce que vous aviez saisi depuis. Quand une mise à jour s'arrête, le Journal nomme désormais la copie prise avant elle (#395).
|
||||
- Import : l'état vide montre la structure attendue (un dossier parent, un sous-dossier par compte), et les cartes du parcours demandent de choisir le dossier parent et de vérifier qu'une dépense compte parmi les sorties (#422).
|
||||
- Parcours de démarrage : la carte Transactions explique les mots-clés et propose « Ajouter comme mot-clé » ; la carte Budget précise que seules les catégories du dernier niveau acceptent un montant et ce que fait la colonne Annuel (#430).
|
||||
|
||||
### Corrigé
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
- The "Parent category" field of the category form and the "Category" field of the "New keyword" dialog (right-click on a transaction) now take typing, like the category cell of the Transactions page: type a few letters to filter the list, which shows the hierarchy with indentation and the names of the standard categories in your language. The parent list no longer offers the category being edited, its own sub-categories, or a parent that would create a fourth level; a parent already stored stays shown. The keyword dialog offers the same categories as the Transactions page (active and allowed for input) and keeps the pre-filled category only when it is among them; its two fields sit above the scrolling list of matches, so the open category list is never cut (#423).
|
||||
- One copy is kept per profile in the `migration-backups` folder: the copy taken before a database update is now taken again at each update, and once the update succeeds, the older copies of that profile are deleted. A copy left by an update that stopped is no longer reused as is, since it predated what you entered after it. When an update stops, the Journal now names the copy taken before it (#395).
|
||||
- Import: the empty state shows the expected structure (a parent folder, one subfolder per account), and the getting-started cards ask you to choose the parent folder and to check that an expense counts among the outflows (#422).
|
||||
- Getting started: the Transactions card explains keywords and suggests “Add as keyword”; the Budget card states that only last-level categories take an amount and what the Annual column does (#430).
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Les six étapes ci-dessous sont celles du parcours de démarrage. La carte d'acc
|
|||
1. Le bandeau de démarrage s'affiche en haut de chaque page tant que le parcours n'est pas masqué. Il indique votre progression, par exemple « Démarrage 3/6 », et l'étape suivante, avec un lien vers la page où la faire. Il n'apparaît pas sur le tableau de bord d'un profil sans transaction, dont la carte d'accueil contient déjà la liste
|
||||
2. « Voir la liste » déplie les six étapes sous le bandeau et « Fermer la liste » les replie ; la liste se replie aussi quand vous changez de page. Chaque étape à faire mène à la page où la faire
|
||||
3. Sur les pages Import, Transactions et Budget, quand l'étape suivante s'y fait, une carte de démarrage remplace le bandeau. Elle indique « Étape n sur N », dit quoi faire et entoure d'un contour le contrôle à utiliser. Le contour pulse trois fois, et pas du tout si votre système demande de réduire les animations. Si vous arrivez par un lien « Étape suivante », le focus du clavier se place sur le titre de la carte
|
||||
4. La carte suit l'étape sans que vous changiez de page. Sur la page Import, elle accompagne l'assistant écran par écran. Le lien de l'étape « Classer une transaction à la main » ouvre la page Transactions sur les transactions non catégorisées, toutes dates confondues, ou sur toutes les transactions s'il n'en reste aucune à classer. Sur la page Budget, la carte désigne d'abord « Tout déplier », puis la cellule du mois courant de la première catégorie à remplir ; elle ne déplie jamais la grille à votre place
|
||||
4. La carte suit l'étape sans que vous changiez de page. Sur la page Import, elle accompagne l'assistant écran par écran. Le lien de l'étape « Classer une transaction à la main » ouvre la page Transactions sur les transactions non catégorisées, toutes dates confondues, ou sur toutes les transactions s'il n'en reste aucune à classer. Sur la page Budget, la carte désigne d'abord « Tout déplier », puis la cellule du mois courant de la première catégorie à remplir ; elle ne déplie jamais la grille à votre place. Sur la page Transactions, la carte propose le clic droit et « Ajouter comme mot-clé » ; sur la page Budget, elle rappelle que seules les catégories du dernier niveau acceptent un montant et ce que fait la colonne Annuel
|
||||
5. Dès que l'étape est faite, la carte reste affichée, annonce « Fait. » et propose l'étape suivante, avec un lien vers sa page. Sur la page Import, elle passe d'une étape à la suivante sans quitter la page et annonce « Fait. » une fois le fichier importé ; jamais pour un import qui n'a ajouté aucune transaction : le rapport dit pourquoi, et l'étape reste à faire
|
||||
6. « Passer cette étape », sur la carte, marque l'étape comme passée : elle compte dans la progression, et la liste l'affiche avec le badge « Passée » et un lien « Reprendre ». « Reprendre » remet l'étape à faire et ouvre sa page
|
||||
7. Quand toutes les étapes sont faites ou passées, le bandeau, ou la carte de la dernière étape, annonce « Démarrage terminé. Le guide d'utilisation vous accompagne pour la suite. » « Ouvrir le guide » mène à cette section Premiers pas, et « Fermer » masque le parcours
|
||||
|
|
|
|||
|
|
@ -258,9 +258,9 @@
|
|||
{ id: "guided-skip-folder", title: "Limite connue : « Passer » sur l'étape du dossier, sans dossier",
|
||||
expect: "Sur un profil neuf, sur la carte Import « Étape 2 sur 6 · Choisir un dossier d'import », clique « Passer cette étape » sans choisir de dossier. La carte passe à « Étape 3 sur 6 · Configurer une source d'import », mais garde la phrase et le contour du choix du dossier ; un second « Passer » mène à l'étape 4, avec la même phrase, et le bandeau revient au troisième. À juger : les étapes de la page dépendent du dossier." },
|
||||
{ id: "guided-empty-profile", title: "Étapes d'import passées : Transactions et Budget sans transaction",
|
||||
expect: "Sur un profil neuf en édition Base ou plus, sur un build de production (le mode dev masque le cas Budget), choisis un dossier d'import vide, puis passe les étapes 3, 4 et 5 avec « Passer cette étape ». Sur la page Transactions, la carte demande de cliquer sur la catégorie d'une transaction au-dessus de l'état vide « Aucune transaction pour l'instant », que la barre de filtres et un bref chargement peuvent précéder (limite connue, à juger). Sur la page Budget, la carte « Étape 6 sur 6 · Saisir un premier budget » garde son titre et « Passer cette étape », sans phrase ni contour, au-dessus de l'état vide « Le budget se construit sur vos transactions » ; elle ne dit jamais « Cliquez Tout déplier… »." },
|
||||
expect: "Sur un profil neuf en édition Base ou plus, sur un build de production (le mode dev masque le cas Budget), choisis un dossier d'import vide, puis passe les étapes 3, 4 et 5 avec « Passer cette étape ». Sur la page Transactions, la carte propose le clic droit et « Ajouter comme mot-clé » au-dessus de l'état vide « Aucune transaction pour l'instant », que la barre de filtres et un bref chargement peuvent précéder (limite connue, à juger). Sur la page Budget, la carte « Étape 6 sur 6 · Saisir un premier budget » garde son titre et « Passer cette étape », sans phrase ni contour, au-dessus de l'état vide « Le budget se construit sur vos transactions » ; elle ne dit jamais « Cliquez « Tout déplier »… »." },
|
||||
{ id: "guided-fallback", title: "Transactions : aucune transaction à classer",
|
||||
expect: "Sur un profil de test où l'étape 5 reste à faire, rends toutes les transactions catégorisées sans les classer à la main : ajoute, depuis la page Catégories, un mot-clé pour chaque transaction restée sans catégorie, puis clique « Auto-catégoriser » sur la page Transactions. Depuis une autre page, suis « Étape suivante : Classer une transaction à la main → ». La page montre toutes les transactions, période « Tout », une seule fois, et la carte dit « Cliquez sur la catégorie d'une transaction… ». « Aucune transaction trouvée. » peut s'afficher un très bref instant entre les deux lectures (limite connue)." },
|
||||
expect: "Sur un profil de test où l'étape 5 reste à faire, rends toutes les transactions catégorisées sans les classer à la main : ajoute, depuis la page Catégories, un mot-clé pour chaque transaction restée sans catégorie, puis clique « Auto-catégoriser » sur la page Transactions. Depuis une autre page, suis « Étape suivante : Classer une transaction à la main → ». La page montre toutes les transactions, période « Tout », une seule fois, et la carte dit « Faites un clic droit sur une transaction, puis « Ajouter comme mot-clé »… ». « Aucune transaction trouvée. » peut s'afficher un très bref instant entre les deux lectures (limite connue)." },
|
||||
{ id: "guided-same-page", title: "Limite connue : un lien d'étape suivi depuis la page Transactions",
|
||||
expect: "Passe l'étape 5 depuis sa carte, change les filtres de la page Transactions, puis, sur cette même page, clique « Reprendre » dans la liste du bandeau. La carte revient, mais la page garde tes filtres au lieu de l'entrée guidée ; la phrase de la carte suit le filtre réellement affiché. À juger." },
|
||||
{ id: "guided-budget-expanded", title: "Budget : grille déjà dépliée par le profil",
|
||||
|
|
@ -295,7 +295,13 @@
|
|||
{ id: "v0171-import-source-name", title: "Import : le nom de la source est celui de son sous-dossier (#426)",
|
||||
expect: "Ouvre une source : la configuration affiche sous « Nom de la source » le nom du sous-dossier, en texte, sans champ de saisie, et dessous « Le nom de la source est celui de son sous-dossier. ». La carte de démarrage dit « Vérifiez les fichiers cochés, puis cliquez « Aperçu ». » et ne parle plus du nom. En anglais : “A source is named after its subfolder.” et “Check the selected files, then click “Preview”.”" },
|
||||
{ id: "v0171-import-source-reopen", title: "Import : la source est retrouvée au prochain import (#426)",
|
||||
expect: "Importe un fichier d'une source, puis « Terminé » et rouvre la même source : aucun bandeau de détection (le format enregistré est restauré tel quel), le fichier déjà importé porte le badge « Importé » et n'est pas coché. Sur une base créée avant ce correctif, une source déjà renommée garde ce défaut (hors périmètre)." }
|
||||
expect: "Importe un fichier d'une source, puis « Terminé » et rouvre la même source : aucun bandeau de détection (le format enregistré est restauré tel quel), le fichier déjà importé porte le badge « Importé » et n'est pas coché. Sur une base créée avant ce correctif, une source déjà renommée garde ce défaut (hors périmètre)." },
|
||||
{ id: "v0171-transactions-card-keyword", title: "Transactions : la carte propose « Ajouter comme mot-clé » (#430)",
|
||||
expect: "Par le lien de l'étape 5, la carte dit : « Seules les transactions non catégorisées sont affichées. Faites un clic droit sur l'une d'elles, puis « Ajouter comme mot-clé » : le mot-clé classe cette transaction et les transactions semblables de l'aperçu, et servira aux prochains imports. Gardez un mot propre au commerçant, pas « PAIEMENT », et décochez les transactions déjà bien classées. Vous pouvez aussi cliquer sur sa catégorie et en choisir une. » Avec le filtre « Toutes », la phrase commence par « Faites un clic droit sur une transaction… » et garde la même consigne. Le contour reste sur la catégorie de la première ligne. En anglais, “Add as keyword” et “PAYMENT” sont entre “ ”." },
|
||||
{ id: "v0171-transactions-keyword-checks", title: "Transactions : la boîte du mot-clé coche l'étape (#430)",
|
||||
expect: "Sur un profil où l'étape 5 reste à faire, suis la carte : clic droit sur une transaction, « Ajouter comme mot-clé », remplace le mot prérempli par un mot propre au commerçant si besoin, choisis une catégorie, décoche une transaction de l'aperçu, puis « Appliquer et recatégoriser ». La carte annonce « Fait. » sans quitter la page ; la transaction décochée garde sa catégorie. La boîte elle-même n'a pas changé." },
|
||||
{ id: "v0171-budget-card-leaves", title: "Budget : la carte nomme les feuilles et la colonne « Annuel » (#430)",
|
||||
expect: "Grille repliée, la carte dit : « Cliquez « Tout déplier » pour afficher vos catégories. Seules celles du dernier niveau, sans chevron, acceptent un montant : cliquez sur un mois et saisissez-le (Entrée enregistre ; Tab enregistre et passe au mois suivant). Un montant saisi dans la colonne « Annuel » est réparti également sur les 12 mois et remplace les montants mensuels. » Après « Tout déplier », la phrase commence par « Seules les catégories du dernier niveau… » et garde la phrase sur « Annuel ». Vérifie dans la grille que l'affirmation tient : une ligne à chevron n'accepte pas de montant ; 120 $ dans « Annuel » donne 10 $ par mois et remplace les montants déjà saisis. En anglais, “Expand all” et “Annual” sont entre “ ”." }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -91,12 +91,12 @@
|
|||
"failed": "The import added no transaction; the report below says why."
|
||||
},
|
||||
"transactions": {
|
||||
"uncategorized": "Only {{filter, lowercase}} transactions are shown. Click the category of one of them and pick one from the list. Simpl'Résultat keeps your choice, and automatic categorization will not replace it.",
|
||||
"all": "Click a transaction's category and pick one from the list. Simpl'Résultat keeps your choice, and automatic categorization will not replace it."
|
||||
"uncategorized": "Only {{filter, lowercase}} transactions are shown. Right-click one of them, then “{{addKeyword}}”: the keyword categorizes it and the similar transactions in the preview, and will apply to future imports. Keep a word specific to the merchant, not “PAYMENT”, and uncheck the transactions already correctly categorized. You can also click its category and pick one.",
|
||||
"all": "Right-click a transaction, then “{{addKeyword}}”: the keyword categorizes it and the similar transactions in the preview, and will apply to future imports. Keep a word specific to the merchant, not “PAYMENT”, and uncheck the transactions already correctly categorized. You can also click its category and pick one."
|
||||
},
|
||||
"budget": {
|
||||
"collapsed": "Click {{expand}} to show your categories, then click a month and enter an amount. Enter saves; Tab saves and moves to the next month.",
|
||||
"flat": "Click a month and enter an amount. Enter saves; Tab saves and moves to the next month."
|
||||
"collapsed": "Click “{{expand}}” to show your categories. Only the last-level ones, without a chevron, take an amount: click a month and type it (Enter saves; Tab saves and moves to the next month). An amount typed in the “{{annual}}” column is split evenly over the 12 months and replaces the monthly amounts.",
|
||||
"flat": "Only last-level categories, without a chevron, take an amount: click a month and type it (Enter saves; Tab saves and moves to the next month). An amount typed in the “{{annual}}” column is split evenly over the 12 months and replaces the monthly amounts."
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
|
|
@ -916,7 +916,7 @@
|
|||
"The getting-started banner shows at the top of every page while the path is not hidden. It shows your progress, for instance \"Getting started 3/6\", and the next step, with a link to the page where it is done. It does not show on the dashboard of a profile without transactions, whose welcome card already holds the list",
|
||||
"\"Show the list\" unfolds the six steps under the banner and \"Close the list\" folds them; the list also folds when you change pages. Each step still to do leads to the page where it is done",
|
||||
"On the Import, Transactions and Budget pages, while the next step is done there, a getting-started card takes the place of the banner. It shows \"Step n of N\", says what to do and puts an outline around the control to use. The outline pulses three times, and not at all if your system asks for reduced motion. When you arrive by a \"Next step\" link, the keyboard focus goes to the card's title",
|
||||
"The card follows the step without you changing pages. On the Import page, it goes along with the wizard screen by screen. The link of the \"Categorize a transaction by hand\" step opens the Transactions page on the uncategorized transactions whatever their date, or on every transaction when none is left uncategorized. On the Budget page, the card first points at \"Expand all\", then at the current month's cell of the first category you can fill in; it never unfolds the grid for you",
|
||||
"The card follows the step without you changing pages. On the Import page, it goes along with the wizard screen by screen. The link of the \"Categorize a transaction by hand\" step opens the Transactions page on the uncategorized transactions whatever their date, or on every transaction when none is left uncategorized. On the Budget page, the card first points at \"Expand all\", then at the current month's cell of the first category you can fill in; it never unfolds the grid for you. On the Transactions page, the card suggests right-clicking and “Add as keyword”; on the Budget page, it recalls that only last-level categories take an amount and what the Annual column does",
|
||||
"As soon as the step is done, the card stays, says \"Done.\" and offers the next step, with a link to its page. On the Import page, it moves from one step to the next without leaving the page and says \"Done.\" once the file is imported; never for an import that added no transaction: the report says why, and the step stays to do",
|
||||
"\"Skip this step\", on the card, marks the step as skipped: it counts towards your progress, and the list shows it with the \"Skipped\" badge and a \"Resume\" link. \"Resume\" puts the step back to do and opens its page",
|
||||
"Once every step is done or skipped, the banner, or the card of the last step, says \"Getting started complete. The user guide takes it from here.\" \"Open the guide\" leads to this Getting Started section, and \"Close\" hides the path",
|
||||
|
|
|
|||
|
|
@ -91,12 +91,12 @@
|
|||
"failed": "L'import n'a ajouté aucune transaction ; le rapport ci-dessous dit pourquoi."
|
||||
},
|
||||
"transactions": {
|
||||
"uncategorized": "Seules les transactions {{filter, lowercase}} sont affichées. Cliquez sur la catégorie de l'une d'elles et choisissez-en une dans la liste. Simpl'Résultat retient ce choix, et la catégorisation automatique ne le remplacera pas.",
|
||||
"all": "Cliquez sur la catégorie d'une transaction et choisissez-en une dans la liste. Simpl'Résultat retient ce choix, et la catégorisation automatique ne le remplacera pas."
|
||||
"uncategorized": "Seules les transactions {{filter, lowercase}} sont affichées. Faites un clic droit sur l'une d'elles, puis « {{addKeyword}} » : le mot-clé classe cette transaction et les transactions semblables de l'aperçu, et servira aux prochains imports. Gardez un mot propre au commerçant, pas « PAIEMENT », et décochez les transactions déjà bien classées. Vous pouvez aussi cliquer sur sa catégorie et en choisir une.",
|
||||
"all": "Faites un clic droit sur une transaction, puis « {{addKeyword}} » : le mot-clé classe cette transaction et les transactions semblables de l'aperçu, et servira aux prochains imports. Gardez un mot propre au commerçant, pas « PAIEMENT », et décochez les transactions déjà bien classées. Vous pouvez aussi cliquer sur sa catégorie et en choisir une."
|
||||
},
|
||||
"budget": {
|
||||
"collapsed": "Cliquez {{expand}} pour afficher vos catégories, puis cliquez sur un mois et saisissez un montant. Entrée enregistre ; Tab enregistre et passe au mois suivant.",
|
||||
"flat": "Cliquez sur un mois et saisissez un montant. Entrée enregistre ; Tab enregistre et passe au mois suivant."
|
||||
"collapsed": "Cliquez « {{expand}} » pour afficher vos catégories. Seules celles du dernier niveau, sans chevron, acceptent un montant : cliquez sur un mois et saisissez-le (Entrée enregistre ; Tab enregistre et passe au mois suivant). Un montant saisi dans la colonne « {{annual}} » est réparti également sur les 12 mois et remplace les montants mensuels.",
|
||||
"flat": "Seules les catégories du dernier niveau, sans chevron, acceptent un montant : cliquez sur un mois et saisissez-le (Entrée enregistre ; Tab enregistre et passe au mois suivant). Un montant saisi dans la colonne « {{annual}} » est réparti également sur les 12 mois et remplace les montants mensuels."
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
|
|
@ -916,7 +916,7 @@
|
|||
"Le bandeau de démarrage s'affiche en haut de chaque page tant que le parcours n'est pas masqué. Il indique votre progression, par exemple « Démarrage 3/6 », et l'étape suivante, avec un lien vers la page où la faire. Il n'apparaît pas sur le tableau de bord d'un profil sans transaction, dont la carte d'accueil contient déjà la liste",
|
||||
"« Voir la liste » déplie les six étapes sous le bandeau et « Fermer la liste » les replie ; la liste se replie aussi quand vous changez de page. Chaque étape à faire mène à la page où la faire",
|
||||
"Sur les pages Import, Transactions et Budget, quand l'étape suivante s'y fait, une carte de démarrage remplace le bandeau. Elle indique « Étape n sur N », dit quoi faire et entoure d'un contour le contrôle à utiliser. Le contour pulse trois fois, et pas du tout si votre système demande de réduire les animations. Si vous arrivez par un lien « Étape suivante », le focus du clavier se place sur le titre de la carte",
|
||||
"La carte suit l'étape sans que vous changiez de page. Sur la page Import, elle accompagne l'assistant écran par écran. Le lien de l'étape « Classer une transaction à la main » ouvre la page Transactions sur les transactions non catégorisées, toutes dates confondues, ou sur toutes les transactions s'il n'en reste aucune à classer. Sur la page Budget, la carte désigne d'abord « Tout déplier », puis la cellule du mois courant de la première catégorie à remplir ; elle ne déplie jamais la grille à votre place",
|
||||
"La carte suit l'étape sans que vous changiez de page. Sur la page Import, elle accompagne l'assistant écran par écran. Le lien de l'étape « Classer une transaction à la main » ouvre la page Transactions sur les transactions non catégorisées, toutes dates confondues, ou sur toutes les transactions s'il n'en reste aucune à classer. Sur la page Budget, la carte désigne d'abord « Tout déplier », puis la cellule du mois courant de la première catégorie à remplir ; elle ne déplie jamais la grille à votre place. Sur la page Transactions, la carte propose le clic droit et « Ajouter comme mot-clé » ; sur la page Budget, elle rappelle que seules les catégories du dernier niveau acceptent un montant et ce que fait la colonne Annuel",
|
||||
"Dès que l'étape est faite, la carte reste affichée, annonce « Fait. » et propose l'étape suivante, avec un lien vers sa page. Sur la page Import, elle passe d'une étape à la suivante sans quitter la page et annonce « Fait. » une fois le fichier importé ; jamais pour un import qui n'a ajouté aucune transaction : le rapport dit pourquoi, et l'étape reste à faire",
|
||||
"« Passer cette étape », sur la carte, marque l'étape comme passée : elle compte dans la progression, et la liste l'affiche avec le badge « Passée » et un lien « Reprendre ». « Reprendre » remet l'étape à faire et ouvre sa page",
|
||||
"Quand toutes les étapes sont faites ou passées, le bandeau, ou la carte de la dernière étape, annonce « Démarrage terminé. Le guide d'utilisation vous accompagne pour la suite. » « Ouvrir le guide » mène à cette section Premiers pas, et « Fermer » masque le parcours",
|
||||
|
|
|
|||
|
|
@ -156,26 +156,26 @@ describe("TransactionsPage — the step card (#399)", () => {
|
|||
expect(html).toContain("t(onboarding.checklist.items.manualCategory)");
|
||||
});
|
||||
|
||||
it("the guided entry: only the uncategorized transactions, the filter named by its own label", () => {
|
||||
it("the guided entry: only the uncategorized transactions, the filter and 'Add as keyword' named by their own labels (#430)", () => {
|
||||
expect(sentence(render(GUIDED))).toBe(
|
||||
"t(onboarding.card.transactions.uncategorized[filter=t(transactions.filters.uncategorized)])",
|
||||
"t(onboarding.card.transactions.uncategorized[filter=t(transactions.filters.uncategorized),addKeyword=t(reports.keyword.addFromTransaction)])",
|
||||
);
|
||||
});
|
||||
|
||||
it("an ordinary visit: the sentence for every transaction", () => {
|
||||
expect(sentence(render())).toBe("t(onboarding.card.transactions.all[])");
|
||||
it("an ordinary visit: the sentence for every transaction, 'Add as keyword' named by its own label (#430)", () => {
|
||||
expect(sentence(render())).toBe("t(onboarding.card.transactions.all[addKeyword=t(reports.keyword.addFromTransaction)])");
|
||||
});
|
||||
|
||||
// The sentence follows the list's filter, not the navigation state.
|
||||
it("the guided entry after its fallback: the sentence for every transaction", () => {
|
||||
harness.filters = { uncategorizedOnly: false };
|
||||
expect(sentence(render(GUIDED))).toBe("t(onboarding.card.transactions.all[])");
|
||||
expect(sentence(render(GUIDED))).toBe("t(onboarding.card.transactions.all[addKeyword=t(reports.keyword.addFromTransaction)])");
|
||||
});
|
||||
|
||||
it("an ordinary visit with 'Uncategorized' picked by hand: the uncategorized sentence", () => {
|
||||
harness.filters = { uncategorizedOnly: true };
|
||||
expect(sentence(render())).toBe(
|
||||
"t(onboarding.card.transactions.uncategorized[filter=t(transactions.filters.uncategorized)])",
|
||||
"t(onboarding.card.transactions.uncategorized[filter=t(transactions.filters.uncategorized),addKeyword=t(reports.keyword.addFromTransaction)])",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,31 @@ describe("applyKeywordWithReassignment", () => {
|
|||
expect(commands.filter((c) => c.startsWith("UPDATE transactions"))).toHaveLength(2);
|
||||
});
|
||||
|
||||
// The Transactions step card leads to this dialog (#430): the transactions it
|
||||
// categorizes must count as categorized by hand, so the step checks off.
|
||||
it("marks every checked transaction as categorized by hand, with a literal 1 (#430)", async () => {
|
||||
mockSelect.mockResolvedValueOnce([]);
|
||||
mockExecute.mockResolvedValue({ lastInsertId: 77, rowsAffected: 1 });
|
||||
|
||||
await applyKeywordWithReassignment({
|
||||
keyword: "METRO",
|
||||
categoryId: 3,
|
||||
transactionIds: [1, 2],
|
||||
allowReplaceExisting: false,
|
||||
});
|
||||
|
||||
const updates = mockExecute.mock.calls.filter((c) =>
|
||||
(c[0] as string).trim().startsWith("UPDATE transactions"),
|
||||
);
|
||||
expect(updates.map((c) => c[1])).toEqual([
|
||||
[3, 1],
|
||||
[3, 2],
|
||||
]);
|
||||
for (const [sql] of updates) {
|
||||
expect((sql as string).replace(/\s+/g, " ")).toContain("is_manually_categorized = 1,");
|
||||
}
|
||||
});
|
||||
|
||||
it("rolls back when an UPDATE throws", async () => {
|
||||
mockSelect.mockResolvedValueOnce([]);
|
||||
mockExecute
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import fr from "../i18n/locales/fr.json";
|
||||
import en from "../i18n/locales/en.json";
|
||||
import { budgetCardModel, transactionsCardModel } from "./onboardingPath";
|
||||
|
||||
type Cards = Record<string, string>;
|
||||
|
||||
|
|
@ -45,3 +46,62 @@ describe("Import step card sentences quote every control they name (#422)", () =
|
|||
expect(interpolations(IMPORT_CARDS[lang].filePreview).sort()).toEqual(["button", "flip"]);
|
||||
});
|
||||
});
|
||||
|
||||
// The Transactions and Budget cards name their controls the same way (#430):
|
||||
// "Add as keyword" on both Transactions sentences, "Expand all" and the
|
||||
// "Annual" column on the Budget ones.
|
||||
type PageCards = { transactions: Cards; budget: Cards };
|
||||
|
||||
const PAGE_CARDS: Record<"fr" | "en", PageCards> = {
|
||||
fr: (fr as { onboarding: { card: PageCards } }).onboarding.card,
|
||||
en: (en as { onboarding: { card: PageCards } }).onboarding.card,
|
||||
};
|
||||
|
||||
/** Every sentence of the two cards, with the model that names its controls. */
|
||||
const PAGE_SENTENCES = [
|
||||
{ page: "transactions", key: "uncategorized", text: transactionsCardModel({ uncategorizedOnly: true }, null).text },
|
||||
{ page: "transactions", key: "all", text: transactionsCardModel({ uncategorizedOnly: false }, null).text },
|
||||
{ page: "budget", key: "collapsed", text: budgetCardModel({ target: "expandAll" }, null).text },
|
||||
{ page: "budget", key: "flat", text: budgetCardModel({ target: "cell" }, null).text },
|
||||
] as const;
|
||||
|
||||
/** The names of every interpolation of a sentence, formatted ones included (`{{filter, lowercase}}`). */
|
||||
function allInterpolations(sentence: string): string[] {
|
||||
return [...sentence.matchAll(/\{\{(\w+)(?:,[^}]*)?\}\}/g)].map((match) => match[1]);
|
||||
}
|
||||
|
||||
describe("Transactions and Budget step card sentences quote every control they name (#430)", () => {
|
||||
it.each(["fr", "en"] as const)("%s: each control interpolation sits between quotation marks", (lang) => {
|
||||
let named = 0;
|
||||
for (const page of ["transactions", "budget"] as const) {
|
||||
for (const [key, sentence] of Object.entries(PAGE_CARDS[lang][page])) {
|
||||
for (const name of interpolations(sentence)) {
|
||||
named += 1;
|
||||
expect(sentence, `onboarding.card.${page}.${key}: ${name}`).toMatch(QUOTED[lang](name));
|
||||
}
|
||||
}
|
||||
}
|
||||
// transactions: uncategorized 1, all 1; budget: collapsed 2, flat 1.
|
||||
expect(named).toBe(5);
|
||||
});
|
||||
|
||||
it.each(["fr", "en"] as const)("%s: the model labels exactly the interpolations of its sentence", (lang) => {
|
||||
for (const { page, key, text } of PAGE_SENTENCES) {
|
||||
expect(text?.key).toBe(`onboarding.card.${page}.${key}`);
|
||||
const sentence = PAGE_CARDS[lang][page][key];
|
||||
expect(Object.keys(text?.labels ?? {}).sort(), `${page}.${key}`).toEqual(
|
||||
allInterpolations(sentence).sort(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["fr", "en"] as const)("%s: both Transactions sentences name 'Add as keyword', both Budget ones the 'Annual' column", (lang) => {
|
||||
for (const { page, key, text } of PAGE_SENTENCES) {
|
||||
const expected = page === "transactions" ? "addKeyword" : "annual";
|
||||
expect(interpolations(PAGE_CARDS[lang][page][key]), `${page}.${key}`).toContain(expected);
|
||||
expect(text?.labels?.[expected], `${page}.${key}`).toBe(
|
||||
page === "transactions" ? "reports.keyword.addFromTransaction" : "budget.annual",
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -610,13 +610,19 @@ describe("transactionsCardModel (#399)", () => {
|
|||
const UNCATEGORIZED: TransactionsCardModel = {
|
||||
text: {
|
||||
key: "onboarding.card.transactions.uncategorized",
|
||||
labels: { filter: "transactions.filters.uncategorized" },
|
||||
labels: {
|
||||
filter: "transactions.filters.uncategorized",
|
||||
addKeyword: "reports.keyword.addFromTransaction",
|
||||
},
|
||||
},
|
||||
done: false,
|
||||
target: "firstRowCategory",
|
||||
};
|
||||
const ALL: TransactionsCardModel = {
|
||||
text: { key: "onboarding.card.transactions.all" },
|
||||
text: {
|
||||
key: "onboarding.card.transactions.all",
|
||||
labels: { addKeyword: "reports.keyword.addFromTransaction" },
|
||||
},
|
||||
done: false,
|
||||
target: "firstRowCategory",
|
||||
};
|
||||
|
|
@ -654,19 +660,19 @@ describe("transactionsCardModel (#399)", () => {
|
|||
|
||||
it("reads the two sentences in French, the filter's label in lowercase", () => {
|
||||
expect(render(UNCATEGORIZED.text, "fr")).toBe(
|
||||
"Seules les transactions non catégorisées sont affichées. Cliquez sur la catégorie de l'une d'elles et choisissez-en une dans la liste. Simpl'Résultat retient ce choix, et la catégorisation automatique ne le remplacera pas.",
|
||||
"Seules les transactions non catégorisées sont affichées. Faites un clic droit sur l'une d'elles, puis « Ajouter comme mot-clé » : le mot-clé classe cette transaction et les transactions semblables de l'aperçu, et servira aux prochains imports. Gardez un mot propre au commerçant, pas « PAIEMENT », et décochez les transactions déjà bien classées. Vous pouvez aussi cliquer sur sa catégorie et en choisir une.",
|
||||
);
|
||||
expect(render(ALL.text, "fr")).toBe(
|
||||
"Cliquez sur la catégorie d'une transaction et choisissez-en une dans la liste. Simpl'Résultat retient ce choix, et la catégorisation automatique ne le remplacera pas.",
|
||||
"Faites un clic droit sur une transaction, puis « Ajouter comme mot-clé » : le mot-clé classe cette transaction et les transactions semblables de l'aperçu, et servira aux prochains imports. Gardez un mot propre au commerçant, pas « PAIEMENT », et décochez les transactions déjà bien classées. Vous pouvez aussi cliquer sur sa catégorie et en choisir une.",
|
||||
);
|
||||
});
|
||||
|
||||
it("reads the two sentences in English, the filter's label in lowercase", () => {
|
||||
expect(render(UNCATEGORIZED.text, "en")).toBe(
|
||||
"Only uncategorized transactions are shown. Click the category of one of them and pick one from the list. Simpl'Résultat keeps your choice, and automatic categorization will not replace it.",
|
||||
"Only uncategorized transactions are shown. Right-click one of them, then “Add as keyword”: the keyword categorizes it and the similar transactions in the preview, and will apply to future imports. Keep a word specific to the merchant, not “PAYMENT”, and uncheck the transactions already correctly categorized. You can also click its category and pick one.",
|
||||
);
|
||||
expect(render(ALL.text, "en")).toBe(
|
||||
"Click a transaction's category and pick one from the list. Simpl'Résultat keeps your choice, and automatic categorization will not replace it.",
|
||||
"Right-click a transaction, then “Add as keyword”: the keyword categorizes it and the similar transactions in the preview, and will apply to future imports. Keep a word specific to the merchant, not “PAYMENT”, and uncheck the transactions already correctly categorized. You can also click its category and pick one.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -677,13 +683,13 @@ describe("budgetCardModel (#400)", () => {
|
|||
const COLLAPSED: BudgetCardModel = {
|
||||
text: {
|
||||
key: "onboarding.card.budget.collapsed",
|
||||
labels: { expand: "reports.collapse.expandAll" },
|
||||
labels: { expand: "reports.collapse.expandAll", annual: "budget.annual" },
|
||||
},
|
||||
done: false,
|
||||
target: "expandAll",
|
||||
};
|
||||
const FLAT: BudgetCardModel = {
|
||||
text: { key: "onboarding.card.budget.flat" },
|
||||
text: { key: "onboarding.card.budget.flat", labels: { annual: "budget.annual" } },
|
||||
done: false,
|
||||
target: "cell",
|
||||
};
|
||||
|
|
@ -724,19 +730,19 @@ describe("budgetCardModel (#400)", () => {
|
|||
|
||||
it("reads the two sentences in French, the button named by its label", () => {
|
||||
expect(render(COLLAPSED.text, "fr")).toBe(
|
||||
"Cliquez Tout déplier pour afficher vos catégories, puis cliquez sur un mois et saisissez un montant. Entrée enregistre ; Tab enregistre et passe au mois suivant.",
|
||||
"Cliquez « Tout déplier » pour afficher vos catégories. Seules celles du dernier niveau, sans chevron, acceptent un montant : cliquez sur un mois et saisissez-le (Entrée enregistre ; Tab enregistre et passe au mois suivant). Un montant saisi dans la colonne « Annuel » est réparti également sur les 12 mois et remplace les montants mensuels.",
|
||||
);
|
||||
expect(render(FLAT.text, "fr")).toBe(
|
||||
"Cliquez sur un mois et saisissez un montant. Entrée enregistre ; Tab enregistre et passe au mois suivant.",
|
||||
"Seules les catégories du dernier niveau, sans chevron, acceptent un montant : cliquez sur un mois et saisissez-le (Entrée enregistre ; Tab enregistre et passe au mois suivant). Un montant saisi dans la colonne « Annuel » est réparti également sur les 12 mois et remplace les montants mensuels.",
|
||||
);
|
||||
});
|
||||
|
||||
it("reads the two sentences in English, the button named by its label", () => {
|
||||
expect(render(COLLAPSED.text, "en")).toBe(
|
||||
"Click Expand all to show your categories, then click a month and enter an amount. Enter saves; Tab saves and moves to the next month.",
|
||||
"Click “Expand all” to show your categories. Only the last-level ones, without a chevron, take an amount: click a month and type it (Enter saves; Tab saves and moves to the next month). An amount typed in the “Annual” column is split evenly over the 12 months and replaces the monthly amounts.",
|
||||
);
|
||||
expect(render(FLAT.text, "en")).toBe(
|
||||
"Click a month and enter an amount. Enter saves; Tab saves and moves to the next month.",
|
||||
"Only last-level categories, without a chevron, take an amount: click a month and type it (Enter saves; Tab saves and moves to the next month). An amount typed in the “Annual” column is split evenly over the 12 months and replaces the monthly amounts.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -364,7 +364,8 @@ export interface TransactionsCardModel extends StepCardModel {
|
|||
* - otherwise, the sentence that matches the list: the uncategorized
|
||||
* transactions only (the guided entry, or the filter picked by hand), the
|
||||
* filter named by its own label, or every transaction (an ordinary visit,
|
||||
* or a guided entry that found none uncategorized); and the category of the
|
||||
* or a guided entry that found none uncategorized); both name the context
|
||||
* menu's "Add as keyword" by its own label (#430); and the category of the
|
||||
* first row shown.
|
||||
*/
|
||||
export function transactionsCardModel(
|
||||
|
|
@ -376,9 +377,15 @@ export function transactionsCardModel(
|
|||
text: filters.uncategorizedOnly
|
||||
? {
|
||||
key: "onboarding.card.transactions.uncategorized",
|
||||
labels: { filter: "transactions.filters.uncategorized" },
|
||||
labels: {
|
||||
filter: "transactions.filters.uncategorized",
|
||||
addKeyword: "reports.keyword.addFromTransaction",
|
||||
},
|
||||
}
|
||||
: { key: "onboarding.card.transactions.all" },
|
||||
: {
|
||||
key: "onboarding.card.transactions.all",
|
||||
labels: { addKeyword: "reports.keyword.addFromTransaction" },
|
||||
},
|
||||
done: false,
|
||||
target: "firstRowCategory",
|
||||
};
|
||||
|
|
@ -398,6 +405,8 @@ export interface BudgetCardModel extends StepCardModel {
|
|||
* grid, the button named by its own label;
|
||||
* - a month cell pointed at: the sentence for a grid whose editable rows
|
||||
* show;
|
||||
* - both sentences say only the last-level rows take an amount and name the
|
||||
* "Annual" column by its own label (#430);
|
||||
* - no target (the grid is not on the page, has no row, or has not said yet):
|
||||
* no sentence and no ring; the title and "Skip this step" stay.
|
||||
*/
|
||||
|
|
@ -411,13 +420,17 @@ export function budgetCardModel(
|
|||
return {
|
||||
text: {
|
||||
key: "onboarding.card.budget.collapsed",
|
||||
labels: { expand: "reports.collapse.expandAll" },
|
||||
labels: { expand: "reports.collapse.expandAll", annual: "budget.annual" },
|
||||
},
|
||||
done: false,
|
||||
target: "expandAll",
|
||||
};
|
||||
case "cell":
|
||||
return { text: { key: "onboarding.card.budget.flat" }, done: false, target: "cell" };
|
||||
return {
|
||||
text: { key: "onboarding.card.budget.flat", labels: { annual: "budget.annual" } },
|
||||
done: false,
|
||||
target: "cell",
|
||||
};
|
||||
default:
|
||||
return { text: null, done: false, target: null };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue