doc: added changelog #139
Merged
doc: added changelog #139
GitHub Actions / Unit Tests
succeeded
May 15, 2026 in 0s
411 passed, 0 failed and 0 skipped
✅ test-results.json
411 tests were completed in 76s with 411 passed, 0 failed and 0 skipped.
| Test suite | Passed | Failed | Skipped | Time |
|---|---|---|---|---|
| test/alerts_test.dart | 3✅ | 704ms | ||
| test/color_test.dart | 32✅ | 92ms | ||
| test/colorblind_filter_test.dart | 38✅ | 96ms | ||
| test/datetime_extension_test.dart | 3✅ | 34ms | ||
| test/datetime_test.dart | 26✅ | 85ms | ||
| test/duration_test.dart | 12✅ | 43ms | ||
| test/file_utilities_test.dart | 25✅ | 102ms | ||
| test/grid_test.dart | 7✅ | 37ms | ||
| test/helpers_test.dart | 14✅ | 82ms | ||
| test/orm_test.dart | 21✅ | 64ms | ||
| test/password_input_test.dart | 7✅ | 1s | ||
| test/separator_extension_test.dart | 24✅ | 67ms | ||
| test/table_controller_test.dart | 9✅ | 48ms | ||
| test/theme_color_test.dart | 26✅ | 77ms | ||
| test/widgets/alerts_test.dart | 3✅ | 720ms | ||
| test/widgets/avatar_test.dart | 18✅ | 1s | ||
| test/widgets/button_test.dart | 7✅ | 953ms | ||
| test/widgets/chip_test.dart | 2✅ | 650ms | ||
| test/widgets/color_picker_test.dart | 12✅ | 2s | ||
| test/widgets/field_error_test.dart | 8✅ | 642ms | ||
| test/widgets/layo_test.dart | 3✅ | 709ms | ||
| test/widgets/number_input_test.dart | 31✅ | 2s | ||
| test/widgets/responsive_row_test.dart | 38✅ | 1s | ||
| test/widgets/table2_test.dart | 21✅ | 22s | ||
| test/widgets/tabs_test.dart | 21✅ | 2s |
✅ test/alerts_test.dart
ThemedAlert widget
✅ renders info alert
✅ renders success alert
✅ renders custom alert with icon and color
✅ test/color_test.dart
toHex()
✅ converts black to #000000
✅ converts white to #FFFFFF
✅ converts kAccentColor (#FF8200)
✅ converts kPrimaryColor (#001e60)
✅ outputs uppercase hex
✅ ignores alpha channel
✅ hex getter is alias for toHex()
toHexWithAlpha()
✅ includes full alpha (#FFFFFFFF for white)
✅ includes zero alpha (#00000000 for transparent)
✅ includes partial alpha (#80FF8200)
✅ alpha comes first in AARRGGBB format
✅ hexWithAlpha getter is alias for toHexWithAlpha()
toInt()
✅ converts to ARGB integer
✅ handles alpha channel correctly
✅ black is 0xFF000000
✅ white is 0xFFFFFFFF
✅ transparent is 0x00000000
fromHex()
✅ parses black #000000
✅ parses white #FFFFFF
✅ parses accent color #FF8200
✅ parses primary color #001E60
✅ parses lowercase hex #ff8200
✅ sets alpha to 255 (fully opaque)
fromHexWithAlpha()
✅ parses full alpha #FFFFFFFF
✅ parses zero alpha #00000000
✅ parses partial alpha #80FF8200
✅ parses alpha first in AARRGGBB format
Round-trip conversions
✅ Color -> toHex() -> fromHex() -> Color preserves RGB
✅ Color -> toHexWithAlpha() -> fromHexWithAlpha() -> Color preserves ARGB
✅ Color -> toInt() -> Color() preserves exact value
JSON aliases
✅ toJson() is equivalent to toHex()
✅ fromJson() is equivalent to fromHex()
✅ test/colorblind_filter_test.dart
protanopiaFilter()
✅ strength 0.0 returns identity matrix
✅ strength 1.0 returns full filter matrix
✅ strength 0.5 returns interpolated matrix
✅ has exactly 20 elements
✅ handles boundary strength values
protanomalyFilter()
✅ strength 0.0 returns identity matrix
✅ strength 1.0 returns full filter matrix
✅ strength 0.5 returns interpolated matrix
✅ has exactly 20 elements
deuteranopiaFilter()
✅ strength 0.0 returns identity matrix
✅ strength 1.0 returns full filter matrix
✅ strength 0.5 returns interpolated matrix
✅ has exactly 20 elements
deuteranomalyFilter()
✅ strength 0.0 returns identity matrix
✅ strength 1.0 returns full filter matrix
✅ strength 0.5 returns interpolated matrix
✅ has exactly 20 elements
tritanopiaFilter()
✅ strength 0.0 returns identity matrix
✅ strength 1.0 returns full filter matrix
✅ strength 0.5 returns interpolated matrix
✅ has exactly 20 elements
tritanomalyFilter()
✅ strength 0.0 returns identity matrix
✅ strength 1.0 returns full filter matrix
✅ strength 0.5 returns interpolated matrix
✅ has exactly 20 elements
ColorblindFilter extension
✅ normal mode returns identity matrix
✅ protanopia mode creates ColorFilter from protanopia matrix
✅ protanomaly mode creates ColorFilter from protanomaly matrix
✅ deuteranopia mode creates ColorFilter from deuteranopia matrix
✅ deuteranomaly mode creates ColorFilter from deuteranomaly matrix
✅ tritanopia mode creates ColorFilter from tritanopia matrix
✅ tritanomaly mode creates ColorFilter from tritanomaly matrix
✅ all modes accept strength parameter
Matrix interpolation behavior
✅ increasing strength gradually changes matrix values
✅ all filters preserve alpha channel (elements 15-19)
Edge cases
✅ negative strength handled (treated as 0 or clamped)
✅ strength > 1.0 handled (extrapolates or clamped)
✅ all filters produce distinct matrices at full strength
✅ test/datetime_extension_test.dart
DateTimeExtension
✅ secondsSinceEpoch returns correct value
✅ thisWeek and lastWeek return correct dates
✅ thisMonth and lastMonth return correct dates
✅ test/datetime_test.dart
thisWeek / lastWeek
✅ returns correct week boundaries
✅ handles Monday correctly
✅ handles Sunday correctly
thisMonth / lastMonth
✅ returns correct month boundaries for August
✅ returns correct lastMonth boundaries
✅ handles February in leap year
✅ handles February in non-leap year
✅ handles January lastMonth wraps to previous year
secondsSinceEpoch
✅ converts milliseconds to seconds
✅ handles epoch zero
format
✅ %Y full year
✅ %y two-digit year
✅ %m zero-padded month
✅ %d zero-padded day
✅ %H 24-hour format
✅ %I 12-hour format
✅ %p AM/PM indicator
✅ %M zero-padded minute
✅ %S zero-padded second
✅ %A full weekday name
✅ %a abbreviated weekday name
✅ %B full month name
✅ %b abbreviated month name
✅ %% literal percent
✅ combined pattern
✅ %I shows 12 for noon/midnight
✅ test/duration_test.dart
Duration.humanize
✅ single unit
✅ singular unit
✅ two units joined with conjunction
✅ three units with delimiter and conjunction
✅ zero duration returns smallest unit with 0
✅ negative duration uses absolute value
✅ filters out zero-value units
✅ lastPrefixComma adds comma before conjunction
✅ custom delimiter
✅ custom units subset
✅ zero duration with seconds in units
✅ large duration includes days and months
✅ test/file_utilities_test.dart
ThemedFile
✅ creates file with name and bytes
✅ creates file with optional path
✅ toString() includes name and size
ThemedFile.mimeType
✅ detects PNG image
✅ detects JPG image
✅ detects JPEG image
✅ detects GIF image
✅ detects SVG image
✅ detects PDF document
✅ detects custom .lc extension
✅ uses path over name when both provided
✅ handles unknown extension
✅ case insensitive extension detection
parseFileToBase64()
✅ converts bytes to base64
✅ includes file name in result
✅ includes mime type in result
✅ returns null when mime type cannot be determined
✅ handles empty file
✅ round-trip base64 encoding/decoding
parseFileToByteArray()
✅ returns file bytes as List<int>
✅ handles empty file
✅ preserves byte values
✅ returns a copy (new list)
globalMimeResolver
✅ includes custom .lc extension
✅ still supports standard extensions
✅ test/grid_test.dart
Sizes.gridSize
✅ each enum returns correct column count
Sizes.boxWidth
✅ col12 returns full width
✅ col6 returns half width
✅ col4 returns one-third width
✅ col3 returns one-quarter width
✅ col1 returns 1/12 of width
✅ formula is (width / 12) * gridSize
✅ test/helpers_test.dart
useBlack
✅ returns true for white (high luminance)
✅ returns false for black (low luminance)
✅ returns true for yellow (high luminance)
✅ returns false for dark blue (low luminance)
✅ respects custom tolerance
validateColor
✅ returns black for light colors
✅ returns white for dark colors
getPrimaryColor
✅ returns kPrimaryColor when null
✅ returns provided color when non-null
getAccentColor
✅ returns kAccentColor when null
✅ returns provided color when non-null
generateSwatch
✅ without shader all shades are the same color
✅ with shader shades have increasing alpha
✅ swatch has all 10 shade levels
✅ test/orm_test.dart
setErrors() and getErrors()
✅ stores and retrieves errors correctly
✅ retrieves empty list for non-existent key
✅ handles multiple errors for same key
✅ replaces existing errors when setErrors called again
hasErrors()
✅ returns true when key has errors
✅ returns false when key has no errors
✅ returns false for non-existent key
hasContainerErrors()
✅ returns true when any key starts with prefix
✅ returns false when no keys match prefix
✅ exact match works as prefix
✅ returns false when errors map is empty
Error code handling without i18n
✅ returns error code as fallback when no i18n set
✅ minLength error includes parameters in fallback
✅ maxLength error includes parameters in fallback
✅ minLength with multiple parameters in fallback
Multiple error codes
✅ handles mix of standard and length errors
Raw errors access
✅ rawErrors returns the internal error map
✅ rawErrors is empty after setting empty map
Edge cases
✅ handles empty error list for key
✅ handles error with empty code
✅ handles special characters in error keys
✅ test/password_input_test.dart
ThemedPasswordInput
✅ renders with label
✅ toggles password visibility on eye icon tap
✅ shows shield check icon for valid password
✅ shows close circle icon for invalid password
✅ shows close circle icon for empty password
✅ hides strength indicator when showLevels is false
✅ calls onChanged when text is entered
✅ test/separator_extension_test.dart
num.w (width)
✅ creates SizedBox with width
✅ handles integer values
✅ handles double values
✅ handles zero
✅ width alias is equivalent to w
num.h (height)
✅ creates SizedBox with height
✅ handles integer values
✅ handles double values
✅ handles zero
✅ height alias is equivalent to h
num.wh (square)
✅ creates SizedBox with equal width and height
✅ handles integer values
✅ handles double values
✅ handles zero
✅ square alias is equivalent to wh
Different num types
✅ works with int
✅ works with double
✅ works with num variable
Edge cases
✅ handles very small values
✅ handles very large values
✅ handles negative values (though unusual in UI)
Common usage patterns
✅ can be used for spacing between widgets
✅ can create responsive spacing
✅ can create square placeholders
✅ test/table_controller_test.dart
ThemedTable2Controller
✅ addListener adds listener to list
✅ removeListener removes specific listener
✅ clearListeners removes all listeners
✅ sort triggers ThemedTable2SortEvent with correct parameters
✅ sort with default parameters
✅ refresh triggers ThemedTable2RefreshEvent
✅ multiple listeners all receive events
✅ dispose clears all listeners
✅ events fire in order listeners were added
✅ test/theme_color_test.dart
getThemeColor() - Predefined themes
✅ PINK returns Colors.pink
✅ RED returns Colors.red
✅ DEEPORANGE returns Colors.deepOrange
✅ ORANGE returns Colors.orange
✅ AMBER returns Colors.amber
✅ YELLOW returns Colors.yellow
✅ LIME returns Colors.lime
✅ LIGHTGREEN returns Colors.lightGreen
✅ GREEN returns Colors.green
✅ TEAL returns Colors.teal
✅ CYAN returns Colors.cyan
✅ LIGHTBLUE returns Colors.lightBlue
✅ BLUE returns Colors.blue
✅ INDIGO returns Colors.indigo
✅ DEEPBLUE returns Colors.deepPurple
✅ PURPLE returns Colors.purple
✅ BLUEGREY returns Colors.blueGrey
✅ GREY returns Colors.grey
✅ BROWN returns Colors.brown
getThemeColor() - CUSTOM theme
✅ CUSTOM with provided color generates swatch from that color
✅ CUSTOM without explicit color uses kPrimaryColor
getThemeColor() - Invalid/default handling
✅ Invalid theme returns swatch from kPrimaryColor
✅ Empty string theme returns swatch from kPrimaryColor
✅ Lowercase theme name does not match (case-sensitive)
getThemeColor() - Return type validation
✅ All valid themes return MaterialColor type
✅ MaterialColor has all required shades (50-900)
✅ test/widgets/alerts_test.dart
ThemedAlert widget
✅ renders info alert
✅ renders success alert
✅ renders custom alert with icon and color
✅ test/widgets/avatar_test.dart
ThemedAvatar - Basic rendering
✅ renders with default size
✅ renders with custom size
✅ renders fallback initials when no avatar provided
✅ renders NA when name is null
ThemedAvatar - Icon rendering
✅ renders icon when provided
✅ icon size is 70% of avatar size by default
✅ respects custom icon size
ThemedAvatar - Name cleaning
✅ cleans special characters from name
✅ handles single character name
✅ returns NA for name with only special characters
✅ takes first 2 characters and uppercases
ThemedAvatar - Tap handlers
✅ calls onTap when tapped
✅ calls onLongTap when long pressed
✅ calls onSecondaryTap when secondary tapped
ThemedAvatar - Elevation and styling
✅ applies custom color
✅ elevation must be between 0 and 5
✅ radius must be greater than or equal to 0
ThemedAvatar - Priority order
✅ icon takes priority over name fallback
✅ test/widgets/button_test.dart
ThemedButton widget
✅ renders with labelText
✅ renders with icon
✅ renders as disabled
✅ renders loading state
✅ renders with custom color
✅ renders with different styles
✅ renders with factory constructors
✅ test/widgets/chip_test.dart
ThemedChip widget
✅ renders with label
✅ renders with custom color
✅ test/widgets/color_picker_test.dart
ThemedColorPicker rendering
✅ renders without crashing
✅ shows label text
✅ shows hex value in text field
✅ shows kPrimaryColor hex when value is null
✅ shows color swatch in prefix
ThemedColorPicker disabled
✅ disabled picker does not open dialog on tap
✅ enabled picker opens dialog on tap
ThemedColorPicker lifecycle
✅ mounts and unmounts without errors
✅ repeated mount/unmount cycles do not throw
✅ didUpdateWidget updates text field when value changes externally
✅ didUpdateWidget resets to kPrimaryColor when value changes to null
✅ didUpdateWidget does not rebuild when same value is passed
✅ test/widgets/field_error_test.dart
ThemedFieldDisplayError
✅ displays single error message
✅ displays multiple errors joined by comma
✅ hides errors when hideDetails is true
✅ shows nothing when errors list is empty
✅ applies custom padding
✅ applies maxLines constraint
✅ error text is red
✅ default maxLines is 1
✅ test/widgets/layo_test.dart
Layo widget
✅ throws assertion error for invalid elevation
✅ renders with default values
✅ renders with custom emotion
✅ test/widgets/number_input_test.dart
ThemedNumberInput rendering
✅ renders without crashing
✅ shows label text
✅ shows initial value formatted in the text field
✅ shows empty field when value is null
✅ shows suffixText when provided
✅ shows prefixText when provided
✅ shows required asterisk when isRequired is true
✅ shows error text when errors list is non-empty
✅ hides error space when hideDetails is true
ThemedNumberInput onChanged via keyboard
✅ calls onChanged with parsed num when user types a number
✅ calls onChanged(null) when user clears the field
✅ does NOT call onChanged when user types only a minus sign
✅ does NOT call onChanged when input cannot be parsed (C3 regression)
✅ calls onChanged with negative number
ThemedNumberInput step buttons
✅ increment button increases value by step
✅ decrement button decreases value by step
✅ step defaults to 1 when not provided
✅ value starts at 0 when null and step button is tapped
✅ increment is disabled at maximum — does not call onChanged
✅ decrement is disabled at minimum — does not call onChanged
✅ step buttons are hidden when hidePrefixSuffixActions is true
✅ step buttons are hidden when disabled is true
ThemedNumberInput cursor position after step
✅ cursor is at end after incrementing from 9 to 10 (digit count changes)
✅ cursor is at end after incrementing from 99 to 100
✅ cursor is at end after decrementing from 10 to 9 (digit count shrinks)
✅ cursor is at end after incrementing into thousands (999 to 1,000)
ThemedNumberInput decimal separators
✅ dot separator formats 1234.5 as "1,234.5"
✅ comma separator formats 1234.5 as "1.234,5"
ThemedNumberInput lifecycle
✅ widget mounts and unmounts without errors
✅ repeated mount/unmount cycles do not throw
✅ controller text clears when value changes to null
✅ test/widgets/responsive_row_test.dart
ResponsiveRow
✅ Renders basic ResponsiveRow with children
✅ ResponsiveRow with empty children renders empty Wrap
✅ ResponsiveRow with single child
✅ ResponsiveRow respects spacing parameter
✅ ResponsiveRow respects spacing = 0
✅ ResponsiveRow default spacing is 0
✅ ResponsiveRow spacing applies to Wrap.runSpacing
✅ ResponsiveRow default spacing gives runSpacing of 0
✅ ResponsiveRow respects mainAxisAlignment
✅ ResponsiveRow respects crossAxisAlignment
✅ ResponsiveRow has full width (width: double.infinity)
✅ ResponsiveRow.builder creates correct number of children
✅ ResponsiveRow.builder with itemCount=0
✅ ResponsiveRow.builder respects mainAxisAlignment
✅ ResponsiveRow.builder respects spacing
✅ ResponsiveRow uses Wrap as layout widget
✅ ResponsiveRow.builder with large itemCount
ResponsiveCol
✅ ResponsiveCol renders child
✅ ResponsiveCol uses xs by default
✅ ResponsiveCol falls back to xs if sm is null
✅ ResponsiveCol with all breakpoints specified
✅ ResponsiveCol uses LayoutBuilder for responsive sizing
✅ ResponsiveCol preserves child widget type
✅ ResponsiveCol with different size values
Sizes enum
✅ Sizes.col1 returns gridSize 1
✅ Sizes.col6 returns gridSize 6
✅ Sizes.col12 returns gridSize 12
✅ boxWidth calculates correct width for col6 at 1200px
✅ boxWidth calculates correct width for col12 at 1200px
✅ boxWidth calculates correct width for col3 at 600px
✅ boxWidth calculates correct width for col1 at 1200px
✅ boxWidth with col2 at 1200px equals 200
✅ boxWidth with col4 at 800px equals 266.67
✅ All sizes have valid gridSize
ResponsiveRow and ResponsiveCol integration
✅ ResponsiveRow with multiple ResponsiveCol children renders correctly
✅ ResponsiveRow with many children
✅ ResponsiveRow.builder with all parameters
✅ ResponsiveCol with ResponsiveRow preserves layout structure
✅ test/widgets/table2_test.dart
ThemedTable2 rendering
✅ renders item values after compute completes
✅ shows CircularProgressIndicator while loading
✅ renders without errors when items is empty
ThemedTable2 column key collision regression
✅ two columns with identical headerText show distinct data
ThemedTable2 search
✅ filters items matching the query
✅ search matches values from all columns
✅ shows all items when search is cleared
ThemedTable2 sort
✅ controller.sort ascending orders items A→Z
✅ controller.sort descending orders items Z→A
✅ numeric values sort as numbers not as strings
✅ controller.refresh re-runs sort without changing order
ThemedTable2 didUpdateWidget
✅ reflects new items when widget rebuilds with a different list
✅ handles growing list without losing existing rows
✅ detects edit in middle of same-length list (DeepCollectionEquality regression)
ThemedTable2 onFilteredCountChanged
✅ fires with full count on initial load
✅ null callback does not throw
✅ fires with 0 on empty items list
✅ fires filtered count after search narrows results
✅ fires updated count when items list grows via didUpdateWidget
✅ fires with same count after controller.sort
✅ does not fire after widget is disposed
✅ test/widgets/tabs_test.dart
ThemedTabView widget
✅ renders with multiple tabs
✅ displays initial tab content
✅ switches tabs on tap
✅ calls onTabIndex callback when tab changes
✅ renders arrow buttons when showArrows is true
✅ arrow buttons exist and are functional
✅ left arrow is disabled on first tab
✅ right arrow is disabled on last tab
✅ handles initialPosition correctly
✅ clamps invalid initialPosition to valid range
✅ persists tab position on widget rebuild
✅ resets tab position when tabs list changes and persistTabPosition is false
✅ renders additionalWidgets
✅ renders with different tab styles
✅ ThemedTab renders with leading icon
✅ ThemedTab renders with trailing icon
✅ renders with custom padding and alignment
✅ arrow button state updates when navigating between tabs (bug regression)
✅ wrapArrowNavigation wraps from last tab to first tab
✅ wrapArrowNavigation wraps from first tab to last tab
✅ wrapArrowNavigation keeps arrows enabled at boundaries
Loading