From 7088c4cfe2d254d58267e2cfb4e541b6c55c7069 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sat, 10 Oct 2026 21:55:44 +0300 Subject: [PATCH 1/2] refactor(erd): extract ErdNoteCard and ErdNoteOps from the view state - ErdNoteCard: the sticky note widget with its menu and resize handle - ErdNoteOps: pure id, placement, drag, resize and nearest-table rules, covered by unit tests without a widget tester --- lib/core/erd/erd_note_ops.dart | 67 ++++++ lib/features/erd/erd_view.dart | 239 +++----------------- lib/features/erd/widgets/erd_note_card.dart | 211 +++++++++++++++++ test/core/erd/erd_note_ops_test.dart | 95 ++++++++ 4 files changed, 408 insertions(+), 204 deletions(-) create mode 100644 lib/core/erd/erd_note_ops.dart create mode 100644 lib/features/erd/widgets/erd_note_card.dart create mode 100644 test/core/erd/erd_note_ops_test.dart diff --git a/lib/core/erd/erd_note_ops.dart b/lib/core/erd/erd_note_ops.dart new file mode 100644 index 00000000..1016ecc5 --- /dev/null +++ b/lib/core/erd/erd_note_ops.dart @@ -0,0 +1,67 @@ +import 'dart:math' show max; +import 'dart:ui' show Offset; + +import 'package:querya_desktop/core/erd/erd_layout.dart'; +import 'package:querya_desktop/core/erd/erd_model.dart'; +import 'package:querya_desktop/core/erd/erd_saved_layout.dart'; + +/// Operations on the sticky notes of a diagram as plain functions (#1362), so +/// the placement, drag and resize rules can be tested without a widget tree. +abstract final class ErdNoteOps { + /// The id for a new note: the first `n` no note has. + static String nextId(List notes) { + var n = 1; + while (notes.any((o) => o.id == 'n$n')) { + n++; + } + return 'n$n'; + } + + /// A new note holding [text], centred on [centre] (canvas space) but never + /// closer than 8 px to the canvas origin. + static ErdNote create( + List notes, { + required String text, + required Offset centre, + }) => + ErdNote( + id: nextId(notes), + text: text, + x: max(8.0, centre.dx - ErdNote.defaultWidth / 2), + y: max(8.0, centre.dy - ErdNote.defaultHeight / 2), + ); + + /// [note] moved by [screenDelta] at zoom [scale], kept on the canvas. + static ErdNote moved(ErdNote note, Offset screenDelta, double scale) { + final d = screenDelta / (scale == 0 ? 1 : scale); + return note.copyWith(x: max(0.0, note.x + d.dx), y: max(0.0, note.y + d.dy)); + } + + /// [note] resized by [screenDelta] at zoom [scale], not below its minimum. + static ErdNote resized(ErdNote note, Offset screenDelta, double scale) { + final d = screenDelta / (scale == 0 ? 1 : scale); + return note.copyWith( + width: max(ErdNote.minWidth, note.width + d.dx), + height: max(ErdNote.minHeight, note.height + d.dy), + ); + } + + /// The table of [visible] nearest to the middle of [note], to attach it to. + static String? nearestTable( + ErdNote note, + ErdLayout layout, + ErdSchema visible, + ) { + final c = Offset(note.x + note.width / 2, note.y + note.height / 2); + String? best; + var bestDistance = double.infinity; + for (final t in visible.tables) { + final d = (layout.rectOf(t).center - c).distance; + if (d < bestDistance) { + bestDistance = d; + best = t.name; + } + } + return best; + } +} diff --git a/lib/features/erd/erd_view.dart b/lib/features/erd/erd_view.dart index 57dfab09..2ab4834a 100644 --- a/lib/features/erd/erd_view.dart +++ b/lib/features/erd/erd_view.dart @@ -5,7 +5,6 @@ import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:file_selector/file_selector.dart'; -import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/material.dart' as material; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart' @@ -18,7 +17,6 @@ import 'package:querya_desktop/features/erd/erd_source.dart'; import 'package:querya_desktop/features/erd/erd_export.dart'; import 'package:querya_desktop/features/erd/erd_export_data.dart'; import 'package:querya_desktop/core/erd/erd_geometry.dart'; -import 'package:querya_desktop/core/erd/erd_note_text.dart'; import 'package:querya_desktop/features/erd/erd_group_dialog.dart'; import 'package:querya_desktop/features/erd/erd_note_dialog.dart'; import 'package:querya_desktop/features/erd/erd_view_name_dialog.dart'; @@ -26,11 +24,13 @@ import 'package:querya_desktop/core/erd/erd_group_ops.dart'; import 'package:querya_desktop/core/erd/erd_layout.dart'; import 'package:querya_desktop/core/erd/erd_layout_engine.dart'; import 'package:querya_desktop/core/erd/erd_model.dart'; +import 'package:querya_desktop/core/erd/erd_note_ops.dart'; import 'package:querya_desktop/core/erd/erd_saved_layout.dart'; import 'package:querya_desktop/core/erd/erd_router.dart'; import 'package:querya_desktop/features/erd/widgets/erd_edge_tip_label.dart'; import 'package:querya_desktop/features/erd/widgets/erd_export_menu.dart'; import 'package:querya_desktop/features/erd/widgets/erd_group_frame.dart'; +import 'package:querya_desktop/features/erd/widgets/erd_note_card.dart'; import 'package:querya_desktop/features/erd/widgets/erd_relation_painter.dart'; import 'package:querya_desktop/features/erd/widgets/erd_search_panel.dart'; import 'package:querya_desktop/features/erd/widgets/erd_status_body.dart'; @@ -604,17 +604,9 @@ class _ErdViewState extends material.State { final text = await showErdNoteDialog(context, title: 'New note', action: 'Add'); if (text == null || !mounted) return; - var n = 1; - while (_notes.any((o) => o.id == 'n$n')) { - n++; - } - final c = _viewCentre(); - setState(() => _notes.add(ErdNote( - id: 'n$n', - text: text, - x: max(8.0, c.dx - ErdNote.defaultWidth / 2), - y: max(8.0, c.dy - ErdNote.defaultHeight / 2), - ))); + final note = + ErdNoteOps.create(_notes, text: text, centre: _viewCentre()); + setState(() => _notes.add(note)); _scheduleSave(); } @@ -640,23 +632,15 @@ class _ErdViewState extends material.State { void _noteDragMove(String id, material.Offset screenDelta) { final i = _notes.indexWhere((n) => n.id == id); if (i < 0) return; - final scale = _scale; - final d = screenDelta / (scale == 0 ? 1 : scale); - final n = _notes[i]; - setState(() => _notes[i] = - n.copyWith(x: max(0.0, n.x + d.dx), y: max(0.0, n.y + d.dy))); + final moved = ErdNoteOps.moved(_notes[i], screenDelta, _scale); + setState(() => _notes[i] = moved); } void _noteResize(String id, material.Offset screenDelta) { final i = _notes.indexWhere((n) => n.id == id); if (i < 0) return; - final scale = _scale; - final d = screenDelta / (scale == 0 ? 1 : scale); - final n = _notes[i]; - setState(() => _notes[i] = n.copyWith( - width: max(ErdNote.minWidth, n.width + d.dx), - height: max(ErdNote.minHeight, n.height + d.dy), - )); + final resized = ErdNoteOps.resized(_notes[i], screenDelta, _scale); + setState(() => _notes[i] = resized); } /// The table nearest to a note's middle, to attach it to. @@ -664,17 +648,7 @@ class _ErdViewState extends material.State { final layout = _layout; final schema = _schema; if (layout == null || schema == null) return null; - final c = material.Offset(n.x + n.width / 2, n.y + n.height / 2); - String? best; - var bestDistance = double.infinity; - for (final t in _visibleOf(schema).tables) { - final d = (layout.rectOf(t).center - c).distance; - if (d < bestDistance) { - bestDistance = d; - best = t.name; - } - } - return best; + return ErdNoteOps.nearestTable(n, layout, _visibleOf(schema)); } /// Canvas size: the cards and every note. @@ -691,174 +665,31 @@ class _ErdViewState extends material.State { /// exports. static const double _noteTint = 0.07; - /// Shadows read only on light surfaces, so a dark canvas gets twice the alpha. - double _shadowAlpha(material.Color canvas) => - canvas.computeLuminance() < 0.5 ? 0.24 : 0.12; - - material.Widget _noteWidget(ErdNote n) { - final wb = context.workbench; - final color = n.color == null ? null : _slotColor(n.color!); - final lines = parseErdNote(n.text); - final base = material.TextStyle( - fontSize: 12, color: Theme.of(context).colorScheme.foreground); - return material.Positioned( - left: n.x, - top: n.y, - width: n.width, - height: n.height, - child: ContextMenu( - items: [ - MenuButton( - key: material.ValueKey('erd_note_edit_${n.id}'), - onPressed: (_) => unawaited(_editNote(n)), - child: const Text('Edit note…'), - ), - MenuButton( - subMenu: [ - MenuButton( - onPressed: (_) => - _updateNote(n.id, (o) => o.copyWith(color: () => null)), - child: const Text('None'), - ), - for (var i = 0; i < erdHeaderSlots.length; i++) - MenuButton( - leading: material.Icon(material.Icons.circle, - size: 12, color: _slotColor(erdHeaderSlots[i])), - onPressed: (_) => _updateNote( - n.id, (o) => o.copyWith(color: () => erdHeaderSlots[i])), - child: Text('Colour ${i + 1}'), - ), - ], - child: const Text('Colour'), - ), - if (n.attachedTo == null) - MenuButton( - key: material.ValueKey('erd_note_attach_${n.id}'), - onPressed: (_) { - final t = _nearestTable(n); - if (t != null) { - _updateNote(n.id, (o) => o.copyWith(attachedTo: () => t)); - } - }, - child: const Text('Attach to nearest table'), - ) - else - MenuButton( - key: material.ValueKey('erd_note_detach_${n.id}'), - onPressed: (_) => - _updateNote(n.id, (o) => o.copyWith(attachedTo: () => null)), - child: Text('Detach from ${n.attachedTo}'), - ), - MenuButton( - key: material.ValueKey('erd_note_delete_${n.id}'), - onPressed: (_) => _deleteNote(n.id), - child: const Text('Delete note'), - ), - ], - child: material.Stack( - children: [ - material.Positioned.fill( - child: material.MouseRegion( - cursor: _draggingNote == n.id - ? material.SystemMouseCursors.grabbing - : material.SystemMouseCursors.grab, - child: material.GestureDetector( - key: material.ValueKey('erd_note_${n.id}'), - dragStartBehavior: DragStartBehavior.down, - // Esc, zoom and search keys work after touching a note. - onTapDown: (_) => _canvasFocus.requestFocus(), - onPanStart: (_) { - _canvasFocus.requestFocus(); - setState(() => _draggingNote = n.id); - }, - onPanUpdate: (d) => _noteDragMove(n.id, d.delta), - onPanEnd: (_) { - setState(() => _draggingNote = null); - _scheduleSave(); - }, - onPanCancel: () => setState(() => _draggingNote = null), - onDoubleTap: () => unawaited(_editNote(n)), - child: material.DecoratedBox( - decoration: material.BoxDecoration( - // Not the canvas colour: a plain note is a slightly - // darker sheet with a shadow, like the cards. - color: material.Color.alphaBlend( - (color ?? wb.mutedForeground).withValues( - alpha: color == null ? _noteTint : 0.18), - wb.surface), - borderRadius: material.BorderRadius.circular(6), - border: material.Border.all( - color: color ?? wb.borderSubtle, - width: n.attachedTo == null ? 1 : 1.5), - boxShadow: [ - material.BoxShadow( - color: wb.shadow - .withValues(alpha: _shadowAlpha(wb.canvas)), - blurRadius: _draggingNote == n.id ? 14 : 6, - offset: material.Offset( - 0, _draggingNote == n.id ? 5 : 2), - ), - ], - ), - child: material.ClipRect( - child: material.Padding( - padding: const material.EdgeInsets.all(8), - child: material.SingleChildScrollView( - physics: const material.NeverScrollableScrollPhysics(), - child: material.Column( - crossAxisAlignment: material.CrossAxisAlignment.start, - children: [ - for (final line in lines) - material.Text.rich( - material.TextSpan( - style: base, - children: [ - if (line.bullet) - const material.TextSpan(text: '• '), - for (final r in line.runs) - material.TextSpan( - text: r.text, - style: r.bold - ? const material.TextStyle( - fontWeight: - material.FontWeight.w700) - : null, - ), - ], - ), - ), - ], - ), - ), - ), - ), - ), - ), - ), - ), - material.Positioned( - right: 0, - bottom: 0, - width: 16, - height: 16, - child: material.MouseRegion( - cursor: material.SystemMouseCursors.resizeDownRight, - child: material.GestureDetector( - key: material.ValueKey('erd_note_resize_${n.id}'), - dragStartBehavior: DragStartBehavior.down, - onPanStart: (_) => _canvasFocus.requestFocus(), - onPanUpdate: (d) => _noteResize(n.id, d.delta), - onPanEnd: (_) => _scheduleSave(), - child: material.Icon(material.Icons.drag_handle_rounded, - size: 12, color: wb.mutedForeground), - ), - ), - ), - ], - ), - ), - ); - } + material.Widget _noteWidget(ErdNote n) => ErdNoteCard( + note: n, + tint: _noteTint, + slotColor: _slotColor, + dragging: _draggingNote == n.id, + onFocusCanvas: _canvasFocus.requestFocus, + onDragStart: () => setState(() => _draggingNote = n.id), + onDragMove: (d) => _noteDragMove(n.id, d), + onDragEnd: () { + setState(() => _draggingNote = null); + _scheduleSave(); + }, + onDragCancel: () => setState(() => _draggingNote = null), + onEdit: () => unawaited(_editNote(n)), + onChange: (change) => _updateNote(n.id, change), + onAttachNearest: () { + final t = _nearestTable(n); + if (t != null) { + _updateNote(n.id, (o) => o.copyWith(attachedTo: () => t)); + } + }, + onDelete: () => _deleteNote(n.id), + onResize: (d) => _noteResize(n.id, d), + onResizeEnd: _scheduleSave, + ); void _groupDragStart(String id) => setState(() => _draggingGroup = id); diff --git a/lib/features/erd/widgets/erd_note_card.dart b/lib/features/erd/widgets/erd_note_card.dart new file mode 100644 index 00000000..958d9eb5 --- /dev/null +++ b/lib/features/erd/widgets/erd_note_card.dart @@ -0,0 +1,211 @@ +import 'package:flutter/gestures.dart' show DragStartBehavior; +import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/erd/erd_note_text.dart'; +import 'package:querya_desktop/core/erd/erd_saved_layout.dart'; +import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +/// A sticky note on the canvas: draggable by its body, resizable by its +/// corner, with an edit / colour / attach / delete menu (#1283). +/// +/// Positioned in canvas space, so it goes straight into the canvas Stack. All +/// changes go out through callbacks; the note itself is immutable. +class ErdNoteCard extends material.StatelessWidget { + const ErdNoteCard({ + super.key, + required this.note, + required this.tint, + required this.slotColor, + required this.dragging, + required this.onFocusCanvas, + required this.onDragStart, + required this.onDragMove, + required this.onDragEnd, + required this.onDragCancel, + required this.onEdit, + required this.onChange, + required this.onAttachNearest, + required this.onDelete, + required this.onResize, + required this.onResizeEnd, + }); + + final ErdNote note; + + /// Tint of a note without a colour over the canvas. + final double tint; + final material.Color Function(String slot) slotColor; + final bool dragging; + final material.VoidCallback onFocusCanvas; + final material.VoidCallback onDragStart; + final void Function(material.Offset screenDelta) onDragMove; + final material.VoidCallback onDragEnd; + final material.VoidCallback onDragCancel; + final material.VoidCallback onEdit; + final void Function(ErdNote Function(ErdNote) change) onChange; + final material.VoidCallback onAttachNearest; + final material.VoidCallback onDelete; + final void Function(material.Offset screenDelta) onResize; + final material.VoidCallback onResizeEnd; + + /// Shadows read only on light surfaces, so a dark canvas gets twice the alpha. + static double _shadowAlpha(material.Color canvas) => + canvas.computeLuminance() < 0.5 ? 0.24 : 0.12; + + @override + material.Widget build(material.BuildContext context) { + final n = note; + final wb = context.workbench; + final color = n.color == null ? null : slotColor(n.color!); + final lines = parseErdNote(n.text); + final base = material.TextStyle( + fontSize: 12, color: Theme.of(context).colorScheme.foreground); + return material.Positioned( + left: n.x, + top: n.y, + width: n.width, + height: n.height, + child: ContextMenu( + items: [ + MenuButton( + key: material.ValueKey('erd_note_edit_${n.id}'), + onPressed: (_) => onEdit(), + child: const Text('Edit note…'), + ), + MenuButton( + subMenu: [ + MenuButton( + onPressed: (_) => onChange((o) => o.copyWith(color: () => null)), + child: const Text('None'), + ), + for (var i = 0; i < erdHeaderSlots.length; i++) + MenuButton( + leading: material.Icon(material.Icons.circle, + size: 12, color: slotColor(erdHeaderSlots[i])), + onPressed: (_) => onChange( + (o) => o.copyWith(color: () => erdHeaderSlots[i])), + child: Text('Colour ${i + 1}'), + ), + ], + child: const Text('Colour'), + ), + if (n.attachedTo == null) + MenuButton( + key: material.ValueKey('erd_note_attach_${n.id}'), + onPressed: (_) => onAttachNearest(), + child: const Text('Attach to nearest table'), + ) + else + MenuButton( + key: material.ValueKey('erd_note_detach_${n.id}'), + onPressed: (_) => + onChange((o) => o.copyWith(attachedTo: () => null)), + child: Text('Detach from ${n.attachedTo}'), + ), + MenuButton( + key: material.ValueKey('erd_note_delete_${n.id}'), + onPressed: (_) => onDelete(), + child: const Text('Delete note'), + ), + ], + child: material.Stack( + children: [ + material.Positioned.fill( + child: material.MouseRegion( + cursor: dragging + ? material.SystemMouseCursors.grabbing + : material.SystemMouseCursors.grab, + child: material.GestureDetector( + key: material.ValueKey('erd_note_${n.id}'), + dragStartBehavior: DragStartBehavior.down, + // Esc, zoom and search keys work after touching a note. + onTapDown: (_) => onFocusCanvas(), + onPanStart: (_) { + onFocusCanvas(); + onDragStart(); + }, + onPanUpdate: (d) => onDragMove(d.delta), + onPanEnd: (_) => onDragEnd(), + onPanCancel: onDragCancel, + onDoubleTap: onEdit, + child: material.DecoratedBox( + decoration: material.BoxDecoration( + // Not the canvas colour: a plain note is a slightly + // darker sheet with a shadow, like the cards. + color: material.Color.alphaBlend( + (color ?? wb.mutedForeground).withValues( + alpha: color == null ? tint : 0.18), + wb.surface), + borderRadius: material.BorderRadius.circular(6), + border: material.Border.all( + color: color ?? wb.borderSubtle, + width: n.attachedTo == null ? 1 : 1.5), + boxShadow: [ + material.BoxShadow( + color: wb.shadow + .withValues(alpha: _shadowAlpha(wb.canvas)), + blurRadius: dragging ? 14 : 6, + offset: material.Offset(0, dragging ? 5 : 2), + ), + ], + ), + child: material.ClipRect( + child: material.Padding( + padding: const material.EdgeInsets.all(8), + child: material.SingleChildScrollView( + physics: const material.NeverScrollableScrollPhysics(), + child: material.Column( + crossAxisAlignment: material.CrossAxisAlignment.start, + children: [ + for (final line in lines) + material.Text.rich( + material.TextSpan( + style: base, + children: [ + if (line.bullet) + const material.TextSpan(text: '• '), + for (final r in line.runs) + material.TextSpan( + text: r.text, + style: r.bold + ? const material.TextStyle( + fontWeight: + material.FontWeight.w700) + : null, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ), + material.Positioned( + right: 0, + bottom: 0, + width: 16, + height: 16, + child: material.MouseRegion( + cursor: material.SystemMouseCursors.resizeDownRight, + child: material.GestureDetector( + key: material.ValueKey('erd_note_resize_${n.id}'), + dragStartBehavior: DragStartBehavior.down, + onPanStart: (_) => onFocusCanvas(), + onPanUpdate: (d) => onResize(d.delta), + onPanEnd: (_) => onResizeEnd(), + child: material.Icon(material.Icons.drag_handle_rounded, + size: 12, color: wb.mutedForeground), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/test/core/erd/erd_note_ops_test.dart b/test/core/erd/erd_note_ops_test.dart new file mode 100644 index 00000000..e06a5142 --- /dev/null +++ b/test/core/erd/erd_note_ops_test.dart @@ -0,0 +1,95 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/erd/erd_layout.dart'; +import 'package:querya_desktop/core/erd/erd_model.dart'; +import 'package:querya_desktop/core/erd/erd_note_ops.dart'; +import 'package:querya_desktop/core/erd/erd_saved_layout.dart'; + +ErdNote _note(String id, {double x = 50, double y = 60}) => + ErdNote(id: id, text: 't', x: x, y: y); + +void main() { + group('ErdNoteOps.nextId', () { + test('starts at n1 and skips taken ids', () { + expect(ErdNoteOps.nextId(const []), 'n1'); + expect(ErdNoteOps.nextId([_note('n1'), _note('n3')]), 'n2'); + }); + }); + + group('ErdNoteOps.create', () { + test('centres the note on the point', () { + final n = ErdNoteOps.create(const [], + text: 'hello', centre: const Offset(500, 400)); + expect(n.text, 'hello'); + expect(n.x, 500 - ErdNote.defaultWidth / 2); + expect(n.y, 400 - ErdNote.defaultHeight / 2); + }); + + test('keeps 8 px from the origin', () { + final n = ErdNoteOps.create(const [], + text: 'x', centre: const Offset(0, 0)); + expect(n.x, 8); + expect(n.y, 8); + }); + }); + + group('ErdNoteOps.moved', () { + test('divides the screen delta by the zoom', () { + final n = ErdNoteOps.moved(_note('n1'), const Offset(20, 10), 2); + expect(n.x, 60); + expect(n.y, 65); + }); + + test('never leaves the canvas', () { + final n = ErdNoteOps.moved(_note('n1', x: 5, y: 5), + const Offset(-100, -100), 1); + expect(n.x, 0); + expect(n.y, 0); + }); + + test('treats a zero scale as 1', () { + final n = ErdNoteOps.moved(_note('n1'), const Offset(10, 0), 0); + expect(n.x, 60); + }); + }); + + group('ErdNoteOps.resized', () { + test('grows by the zoomed delta', () { + final n = ErdNoteOps.resized(_note('n1'), const Offset(40, 20), 2); + expect(n.width, ErdNote.defaultWidth + 20); + expect(n.height, ErdNote.defaultHeight + 10); + }); + + test('stops at the minimum size', () { + final n = ErdNoteOps.resized(_note('n1'), const Offset(-999, -999), 1); + expect(n.width, ErdNote.minWidth); + expect(n.height, ErdNote.minHeight); + }); + }); + + group('ErdNoteOps.nearestTable', () { + test('picks the table whose card is closest to the note', () { + final schema = ErdSchema( + tables: [ + const ErdTable(name: 'a', columns: []), + const ErdTable(name: 'b', columns: []), + ], + relations: const [], + ); + final layout = ErdLayout.compute(schema); + final near = layout.rectOf(schema.tables[1]).center; + final note = ErdNote( + id: 'n1', + text: 't', + x: near.dx - ErdNote.defaultWidth / 2, + y: near.dy - ErdNote.defaultHeight / 2, + ); + expect(ErdNoteOps.nearestTable(note, layout, schema), 'b'); + }); + + test('is null for a diagram without tables', () { + const schema = ErdSchema(tables: [], relations: []); + final layout = ErdLayout.compute(schema); + expect(ErdNoteOps.nearestTable(_note('n1'), layout, schema), isNull); + }); + }); +} From 099ac8b1968b8f0e45232aaefc150607a2f4fe4a Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sat, 10 Oct 2026 22:07:06 +0300 Subject: [PATCH 2/2] test(erd): make the note ops test schema const --- test/core/erd/erd_note_ops_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/core/erd/erd_note_ops_test.dart b/test/core/erd/erd_note_ops_test.dart index e06a5142..63f4af1e 100644 --- a/test/core/erd/erd_note_ops_test.dart +++ b/test/core/erd/erd_note_ops_test.dart @@ -68,12 +68,12 @@ void main() { group('ErdNoteOps.nearestTable', () { test('picks the table whose card is closest to the note', () { - final schema = ErdSchema( + const schema = ErdSchema( tables: [ - const ErdTable(name: 'a', columns: []), - const ErdTable(name: 'b', columns: []), + ErdTable(name: 'a', columns: []), + ErdTable(name: 'b', columns: []), ], - relations: const [], + relations: [], ); final layout = ErdLayout.compute(schema); final near = layout.rectOf(schema.tables[1]).center;