Phase 1.1.1 : consolidation et coherence UX
Trois corrections ciblees, sans changement d'architecture ni d'interface. 1. Le style ne transporte plus l'icone L'icone d'un noeud est du contenu, comme son texte ou sa note. Copier le style de "Reseau" et le coller sur "Stockage" et "Securite" applique desormais les couleurs, la forme et la mise en page en laissant chaque noeud avec sa propre icone. Seule la presentation de l'icone (position, taille, espacement) reste une propriete de style. 2. Le cercle conserve un rapport 1:1 `keepAspectRatio` sur le redimensionneur, et contrainte cote store pour les trois autres chemins : redimensionnement direct, saisie d'une largeur ou d'une hauteur dans le panneau, et conversion d'un noeud rectangulaire en cercle. Un cercle ne peut plus devenir un ovale. 3. Point d'entree unique des documents `normalizeMapDocument()` remplace `parseMapDocument()` et devient le seul chemin par lequel une carte entre dans l'editeur, qu'elle vienne d'un fichier .mmap, d'IndexedDB ou d'un partage. Le pipeline est toujours validation -> migration -> normalisation. Les cartes locales passaient jusqu'ici a cote de la migration : elles y passent maintenant a l'ouverture, sans etre reecrites en base tant que l'utilisateur ne les modifie pas. Une carte locale videe de ses noeuds reste ouvrable, et le message d'erreur s'adapte a la provenance. Egalement - Zoom minimum abaisse de 0.1 a 0.02 : "Adapter a l'ecran" butait sur ce plancher et ne montrait pas toute une carte de 181 noeuds. - README : pipeline de normalisation, comportement du style et du cercle, limitations mesurees. Tests : 103 vitest (85 -> 103) et 31 pytest, ruff, eslint et tsc au vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
126
frontend/src/stores/editorCircle.test.ts
Normal file
126
frontend/src/stores/editorCircle.test.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { createEmptyMap } from '@/services/mapFactory';
|
||||
import { getTheme } from '@/features/editor/themes';
|
||||
import { resolveNodeAppearance } from '@/features/editor/nodeStyleResolver';
|
||||
import { estimateNodeSize } from '@/utils/nodeSize';
|
||||
import { useEditorStore } from '@/stores/editorStore';
|
||||
|
||||
function store() {
|
||||
return useEditorStore.getState();
|
||||
}
|
||||
|
||||
function rootId(): string {
|
||||
return store().map?.nodes[0]?.id as string;
|
||||
}
|
||||
|
||||
function nodeById(id: string) {
|
||||
return store().map?.nodes.find((node) => node.id === id);
|
||||
}
|
||||
|
||||
function layoutOf(id: string) {
|
||||
return nodeById(id)?.layout;
|
||||
}
|
||||
|
||||
/** Cree un noeud circulaire et renvoie son identifiant. */
|
||||
function circleNode(): string {
|
||||
const id = store().addChild(rootId()) as string;
|
||||
store().setEditingNode(null);
|
||||
store().updateNodeText(id, 'Coeur de reseau');
|
||||
store().updateNodesStyle([id], { shape: 'circle' });
|
||||
return id;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
useEditorStore.getState().loadMap(createEmptyMap('Cercles'));
|
||||
});
|
||||
|
||||
describe('noeud circulaire', () => {
|
||||
it('reste carre en dimensionnement automatique', () => {
|
||||
const id = circleNode();
|
||||
const node = nodeById(id);
|
||||
const size = estimateNodeSize(node!, getTheme('default'));
|
||||
|
||||
expect(size.width).toBe(size.height);
|
||||
expect(resolveNodeAppearance(node!, 1, getTheme('default')).circle).toBe(true);
|
||||
});
|
||||
|
||||
it('reste carre pendant un redimensionnement a la souris', () => {
|
||||
const id = circleNode();
|
||||
store().beginResize();
|
||||
|
||||
for (const [width, height] of [
|
||||
[100, 100],
|
||||
[150, 148],
|
||||
[220, 90],
|
||||
] as Array<[number, number]>) {
|
||||
store().resizeNode(id, { width, height });
|
||||
expect(layoutOf(id)?.width).toBe(layoutOf(id)?.height);
|
||||
}
|
||||
|
||||
// Le cote retenu est le plus grand des deux, jamais un ovale.
|
||||
expect(layoutOf(id)?.width).toBe(220);
|
||||
expect(layoutOf(id)?.height).toBe(220);
|
||||
});
|
||||
|
||||
it('fait suivre la hauteur quand la largeur change', () => {
|
||||
const id = circleNode();
|
||||
store().updateNodesLayout([id], { widthMode: 'fixed', width: 180 });
|
||||
|
||||
expect(layoutOf(id)?.width).toBe(180);
|
||||
expect(layoutOf(id)?.height).toBe(180);
|
||||
expect(layoutOf(id)?.heightMode).toBe('fixed');
|
||||
});
|
||||
|
||||
it('fait suivre la largeur quand la hauteur change', () => {
|
||||
const id = circleNode();
|
||||
store().updateNodesLayout([id], { heightMode: 'fixed', height: 260 });
|
||||
|
||||
expect(layoutOf(id)?.width).toBe(260);
|
||||
expect(layoutOf(id)?.height).toBe(260);
|
||||
expect(layoutOf(id)?.widthMode).toBe('fixed');
|
||||
});
|
||||
|
||||
it('carre un noeud rectangulaire converti en cercle', () => {
|
||||
const id = store().addChild(rootId()) as string;
|
||||
store().setEditingNode(null);
|
||||
store().updateNodesLayout([id], {
|
||||
widthMode: 'fixed',
|
||||
width: 320,
|
||||
heightMode: 'fixed',
|
||||
height: 90,
|
||||
});
|
||||
|
||||
store().updateNodesStyle([id], { shape: 'circle' });
|
||||
|
||||
expect(layoutOf(id)?.width).toBe(320);
|
||||
expect(layoutOf(id)?.height).toBe(320);
|
||||
});
|
||||
|
||||
it('annule tout un redimensionnement circulaire en une seule fois', () => {
|
||||
const id = circleNode();
|
||||
const before = store().past.length;
|
||||
|
||||
store().beginResize();
|
||||
for (let side = 120; side <= 200; side += 20) {
|
||||
store().resizeNode(id, { width: side, height: side });
|
||||
}
|
||||
expect(store().past.length).toBe(before + 1);
|
||||
|
||||
store().undo();
|
||||
expect(layoutOf(id)?.width).toBeUndefined();
|
||||
expect(layoutOf(id)?.height).toBeUndefined();
|
||||
expect(nodeById(id)?.style?.shape).toBe('circle');
|
||||
});
|
||||
|
||||
it('laisse les autres formes libres de leurs proportions', () => {
|
||||
const id = store().addChild(rootId()) as string;
|
||||
store().setEditingNode(null);
|
||||
store().updateNodesStyle([id], { shape: 'rounded' });
|
||||
|
||||
store().beginResize();
|
||||
store().resizeNode(id, { width: 300, height: 80 });
|
||||
|
||||
expect(layoutOf(id)?.width).toBe(300);
|
||||
expect(layoutOf(id)?.height).toBe(80);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user