markdown_exec 2.8.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -1
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +0 -33
  5. data/bats/bats.bats +2 -0
  6. data/bats/block-type-link.bats +1 -1
  7. data/bats/block-type-ux-allowed.bats +2 -2
  8. data/bats/block-type-ux-invalid.bats +1 -1
  9. data/bats/{block-type-ux-preconditions.bats → block-type-ux-required-variables.bats} +1 -1
  10. data/bats/block-type-ux-row-format.bats +1 -1
  11. data/bats/block-type-ux-sources.bats +36 -0
  12. data/bats/border.bats +1 -1
  13. data/bats/cli.bats +2 -2
  14. data/bats/command-substitution-options.bats +14 -0
  15. data/bats/command-substitution.bats +1 -1
  16. data/bats/fail.bats +5 -2
  17. data/bats/indented-block-type-vars.bats +1 -1
  18. data/bats/markup.bats +1 -1
  19. data/bats/option-expansion.bats +8 -0
  20. data/bats/table-column-truncate.bats +1 -1
  21. data/bats/test_helper.bash +50 -5
  22. data/docs/dev/bats-document-configuration.md +1 -1
  23. data/docs/dev/block-type-ux-allowed.md +5 -7
  24. data/docs/dev/block-type-ux-auto.md +8 -5
  25. data/docs/dev/block-type-ux-chained.md +4 -2
  26. data/docs/dev/block-type-ux-echo-hash.md +6 -7
  27. data/docs/dev/block-type-ux-echo.md +2 -2
  28. data/docs/dev/block-type-ux-exec.md +3 -5
  29. data/docs/dev/block-type-ux-hidden.md +3 -0
  30. data/docs/dev/{block-type-ux-preconditions.md → block-type-ux-required-variables.md} +1 -2
  31. data/docs/dev/block-type-ux-row-format.md +3 -4
  32. data/docs/dev/block-type-ux-sources.md +57 -0
  33. data/docs/dev/block-type-ux-transform.md +0 -4
  34. data/docs/dev/command-substitution-options.md +61 -0
  35. data/docs/dev/indented-block-type-vars.md +1 -0
  36. data/docs/dev/menu-pagination-indent.md +123 -0
  37. data/docs/dev/menu-pagination.md +111 -0
  38. data/docs/dev/option-expansion.md +10 -0
  39. data/lib/ansi_formatter.rb +2 -0
  40. data/lib/block_cache.rb +197 -0
  41. data/lib/command_result.rb +57 -0
  42. data/lib/constants.rb +18 -0
  43. data/lib/error_reporting.rb +38 -0
  44. data/lib/evaluate_shell_expressions.rb +43 -18
  45. data/lib/fcb.rb +98 -7
  46. data/lib/hash_delegator.rb +526 -322
  47. data/lib/markdown_exec/version.rb +1 -1
  48. data/lib/markdown_exec.rb +136 -45
  49. data/lib/mdoc.rb +59 -10
  50. data/lib/menu.src.yml +23 -11
  51. data/lib/menu.yml +22 -12
  52. data/lib/value_or_exception.rb +76 -0
  53. metadata +16 -4
@@ -4,17 +4,20 @@ ENTITY='Pongo tapanuliensis,Pongo'
4
4
  ```
5
5
  ```ux :[SPECIES] +(shell) +(GENUS)
6
6
  echo: "${ENTITY%%,*}"
7
+ init: false
7
8
  name: SPECIES
8
9
  ```
9
10
  / required ux block requires another
10
11
  ```ux :(GENUS) +[NAME]
11
12
  echo: "${ENTITY##*,}"
13
+ init: false
12
14
  name: GENUS
13
15
  readonly: true
14
16
  ```
15
17
  / executed in context of prior ux blocks, uses their values
16
18
  ```ux :[NAME]
17
19
  echo: "$SPECIES - $GENUS"
20
+ init: false
18
21
  name: NAME
19
22
  readonly: true
20
23
  ```
@@ -1,9 +1,8 @@
1
1
  / an automatic UX block that has a precondition that must be met before it is executed
2
2
  ```ux :[document_ux_SPECIES]
3
- default: :exec
4
3
  exec: printf "$MISSING_VARIABLE"
5
4
  name: SPECIES
6
- preconditions:
5
+ required:
7
6
  - MISSING_VARIABLE
8
7
  ```
9
8
  @import bats-document-configuration.md
@@ -10,7 +10,7 @@ allowed:
10
10
  - Pongo tapanuliensis
11
11
  - Histiophryne psychedelica
12
12
  - Phyllopteryx dewysea
13
- default: Pongo tapanuliensis
13
+ init: Pongo tapanuliensis
14
14
  name: Species
15
15
  prompt: New species?
16
16
  ```
@@ -24,7 +24,6 @@ allowed:
24
24
  - 1. Pongo
25
25
  - 2. Histiophryne
26
26
  - 3. Phyllopteryx
27
- default: Pongo
28
27
  menu_format: "| Name: %{name}| Value: ${%{name}}| Prompt: %{prompt}"
29
28
  name: Genus
30
29
  prompt: New genus?
@@ -35,13 +34,13 @@ validate: |
35
34
  / default
36
35
  / auto-load default value
37
36
  ```ux :[document_ux_Family]
38
- default: Hominidae
37
+ init: Hominidae
39
38
  name: Family
40
39
  ```
41
40
  @import bats-document-configuration.md
42
41
  ```opts :(document_opts)
43
42
  menu_ux_row_format: '| %{name}| ${%{name}}| %{prompt}'
44
- screen_width: 64
43
+ screen_width: 72
45
44
  table_center: true
46
45
  ux_auto_load_force_default: true
47
46
  ```
@@ -0,0 +1,57 @@
1
+ / 1. Simple variable display and edit:
2
+ ```ux
3
+ name: USER_NAME
4
+ prompt: Enter your name
5
+ default: Guest
6
+ ```
7
+ /Behavior: Displays the USER_NAME variable. When clicked, prompts for input with "Enter your name" and defaults to "Guest" if no input is provided.
8
+ /
9
+ /2. Command output initialization:
10
+ ```ux
11
+ name: CURRENT_DIR
12
+ init: :exec
13
+ exec: basename $(pwd)
14
+ transform: :chomp
15
+ ```
16
+ /Behavior: Initializes CURRENT_DIR with the output of the `pwd` command when the document loads.
17
+ /
18
+ /3. Echo-based initialization:
19
+ ```ux
20
+ name: SHELL_VERSION
21
+ init: :echo
22
+ echo: $SHELL
23
+ ```
24
+ /Behavior: Sets SHELL_VERSION to the value of the $SHELL environment variable when the document loads.
25
+ /
26
+ /4. Selection from allowed values:
27
+ ```ux
28
+ name: ENVIRONMENT
29
+ allow:
30
+ - development
31
+ - staging
32
+ - production
33
+ prompt: Select environment
34
+ ```
35
+ /Behavior: When activated, presents a menu to select from development, staging, or production environments.
36
+ /
37
+ /## Validation Examples
38
+ /
39
+ /5. Email validation:
40
+ ```ux
41
+ name: USER_EMAIL
42
+ prompt: Enter email address
43
+ validate: '(?<local>[^@]+)@(?<domain>[^@]+)'
44
+ transform: '%{local}@%{domain}'
45
+ ```
46
+ /Behavior: Validates input as an email address, capturing local and domain parts. The transform ensures proper formatting.
47
+ /
48
+ /6. Version number validation:
49
+ ```ux
50
+ name: VERSION
51
+ prompt: Enter version number
52
+ validate: '(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)'
53
+ transform: '%{major}.%{minor}.%{patch}'
54
+ ```
55
+ /Behavior: Ensures input follows semantic versioning format (e.g., 1.2.3).
56
+ /
57
+ @import bats-document-configuration.md
@@ -1,7 +1,6 @@
1
1
  :::
2
2
  **Execution output has a trailing newline.**
3
3
  ```ux :[document_ux_transform_0]
4
- default: :exec
5
4
  exec: basename $(pwd)
6
5
  name: Var0
7
6
  ```
@@ -9,7 +8,6 @@ $(echo -n "$Var0" | hexdump -C)
9
8
  :::
10
9
  **With validate and transform, output has no newline.**
11
10
  ```ux :[document_ux_transform_1]
12
- default: :exec
13
11
  exec: basename $(pwd)
14
12
  name: Var1
15
13
  transform: '%{name}'
@@ -19,7 +17,6 @@ $(echo -n "$Var1" | hexdump -C)
19
17
  :::
20
18
  **With transform `:chomp`, output has no newline.**
21
19
  ```ux :[document_ux_transform_2]
22
- default: :exec
23
20
  exec: basename $(pwd)
24
21
  name: Var2
25
22
  transform: :chomp
@@ -28,7 +25,6 @@ $(echo -n "$Var2" | hexdump -C)
28
25
  :::
29
26
  **With transform `:upcase`, output is in upper case w/ newline.**
30
27
  ```ux :[document_ux_transform_3]
31
- default: :exec
32
28
  exec: basename $(pwd)
33
29
  name: Var3
34
30
  transform: :upcase
@@ -0,0 +1,61 @@
1
+ / command substitution options to use different patterns
2
+ /
3
+ @import bats-document-configuration.md
4
+ /
5
+ ```vars :(document_vars)
6
+ Common_Name: Tapanuli Orangutan
7
+ Species: Pongo tapanuliensis
8
+ ```
9
+ /
10
+ ::: Command substitution
11
+
12
+ The current value of environment variable `Common_Name` is displayed using two different operators.
13
+ The command `echo $SHLVL` is executed via command substitution, using two different operators.
14
+
15
+ | Operator| Variable Expansion| Command Substitution
16
+ | -| -| -
17
+ | $| ${Common_Name}| $(echo $Species)
18
+ | @| @{Common_Name}| @(echo $Species)
19
+
20
+ ::: Toggle between operators.
21
+
22
+ /| MDE Option| Value
23
+ /| -| -
24
+ /| command_substitution_regexp| &{command_substitution_regexp}
25
+ /| menu_ux_row_format| &{menu_ux_row_format}
26
+ /| variable_expansion_regexp| &{variable_expansion_regexp}
27
+
28
+ / This block requires a hidden block.
29
+ ```opts :operator_$ +(operator_$2)
30
+ command_substitution_regexp: '(?<expression>\$\((?<command>([^()]*(\([^()]*\))*[^()]*)*)\))'
31
+ ```
32
+ ```opts :(operator_$2)
33
+ menu_ux_row_format: '%{name}=${%{name}}'
34
+ variable_expansion_regexp: '(?<expression>\${(?<variable>[A-Z0-9a-z_]+)})'
35
+ ```
36
+
37
+ ```opts :operator_@
38
+ command_substitution_regexp: '(?<expression>@\((?<command>([^()]*(\([^()]*\))*[^()]*)*)\))'
39
+ menu_ux_row_format: '%{name}=@{%{name}}'
40
+ variable_expansion_regexp: '(?<expression>@{(?<variable>[A-Z0-9a-z_]+)})'
41
+ ```
42
+
43
+ ```opts :(both)
44
+ command_substitution_regexp: '(?<expression>[\$\@]\((?<command>([^()]*(\([^()]*\))*[^()]*)*)\))'
45
+ menu_ux_row_format: '%{name}=${%{name}}'
46
+ variable_expansion_regexp: '(?<expression>[\$\@]{(?<variable>[A-Z0-9a-z_]+)})'
47
+ ```
48
+ /
49
+ / Require the block that sets @ as the operator.
50
+ ```opts :(document_opts) +operator_@
51
+ divider4_collapsible: false
52
+ heading1_center: false
53
+ heading2_center: false
54
+ heading3_center: false
55
+ menu_final_divider:
56
+ menu_for_saved_lines: false
57
+ menu_initial_divider:
58
+ menu_vars_set_format:
59
+ screen_width: 64
60
+ table_center: false
61
+ ```
@@ -1,4 +1,5 @@
1
1
  / All lines of an indented block must have equal indents.
2
+ / The block is indented with 2 spaces.
2
3
  ```vars
3
4
  Species: Pongo tapanuliensis
4
5
  Genus: Pongo
@@ -0,0 +1,123 @@
1
+ # Menu pagination indent
2
+
3
+ | Option| Value
4
+ | -| -
5
+ | select_page_height| &{select_page_height}
6
+ | screen_width| &{screen_width}
7
+
8
+ / An Opts block
9
+ ::: Indented with 2 spaces
10
+ ```opts
11
+ option1: 1
12
+ option2: 2
13
+ option3: 3
14
+ option4: 4
15
+ ```
16
+ ::: Indented with 4 spaces
17
+ ```opts
18
+ option1: 1
19
+ option2: 2
20
+ option3: 3
21
+ option4: 4
22
+ ```
23
+ ::: Indented with 1 tab
24
+ ```opts
25
+ option1: 1
26
+ option2: 2
27
+ option3: 3
28
+ option4: 4
29
+ ```
30
+
31
+ / A shell block
32
+ ::: Indented with 2 spaces
33
+ ```
34
+ : 1
35
+ : 2
36
+ : 3
37
+ : 4
38
+ ```
39
+ ::: Indented with 4 spaces
40
+ ```
41
+ : 1
42
+ : 2
43
+ : 3
44
+ : 4
45
+ ```
46
+ ::: Indented with 1 tab
47
+ ```
48
+ : 1
49
+ : 2
50
+ : 3
51
+ : 4
52
+ ```
53
+
54
+ | Variable| Value
55
+ | -| -
56
+ | VARIABLE| ${VARIABLE}
57
+
58
+ / An UX block
59
+ ::: Indented with 2 spaces
60
+ ```ux
61
+ init: false
62
+ echo:
63
+ VARIABLE1: 1
64
+ VARIABLE2: 2
65
+ VARIABLE3: 3
66
+ VARIABLE4: 4
67
+ name: VARIABLE1
68
+ ```
69
+ ::: Indented with 4 spaces
70
+ ```ux
71
+ init: false
72
+ echo:
73
+ VARIABLE1: 1
74
+ VARIABLE2: 2
75
+ VARIABLE3: 3
76
+ VARIABLE4: 4
77
+ name: VARIABLE1
78
+ ```
79
+ ::: Indented with 1 tab
80
+ ```ux
81
+ init: false
82
+ echo:
83
+ VARIABLE1: 1
84
+ VARIABLE2: 2
85
+ VARIABLE3: 3
86
+ VARIABLE4: 4
87
+ name: VARIABLE1
88
+ ```
89
+
90
+
91
+ | Variable| Value
92
+ | -| -
93
+ | VARIABLE1| ${VARIABLE1}
94
+ | VARIABLE2| ${VARIABLE2}
95
+
96
+ / A VARS block
97
+ ::: Indented with 2 spaces
98
+ ```vars
99
+ VARIABLE1: 1
100
+ VARIABLE2: 2
101
+ VARIABLE3: 3
102
+ VARIABLE4: 4
103
+ ```
104
+ ::: Indented with 4 spaces
105
+ ```vars
106
+ VARIABLE1: 1
107
+ VARIABLE2: 2
108
+ VARIABLE3: 3
109
+ VARIABLE4: 4
110
+ ```
111
+ ::: Indented with 1 tab
112
+ ```vars
113
+ VARIABLE1: 1
114
+ VARIABLE2: 2
115
+ VARIABLE3: 3
116
+ VARIABLE4: 4
117
+ ```
118
+
119
+ @import bats-document-configuration.md
120
+ ```opts :(document_opts)
121
+ screen_width: 48
122
+ select_page_height: 12
123
+ ```
@@ -0,0 +1,111 @@
1
+ # Menu pagination
2
+
3
+ ::: ˆ
4
+ MDE detects the screen's dimensions: height (lines) and width (characters)
5
+
6
+ | Option| Value
7
+ | -| -
8
+ | select_page_height| &{select_page_height}
9
+ | screen_width| &{screen_width}
10
+
11
+ Normal document text is displayed as disabled menu lines. The width of these lines is limited according to the screen's width.
12
+
13
+ ::: ˆ
14
+ / A sequence of text blocks
15
+ : 1
16
+ : 2
17
+ : 3
18
+ : 4
19
+ : 5
20
+ : 6
21
+ : 7
22
+ : 8
23
+ : 9
24
+ : 10
25
+ : 11
26
+ : 12
27
+
28
+ ::: ˆ
29
+ / A shell block
30
+ ```
31
+ : 1
32
+ : 2
33
+ : 3
34
+ : 4
35
+ : 5
36
+ : 6
37
+ : 7
38
+ : 8
39
+ : 9
40
+ : 10
41
+ : 11
42
+ : 12
43
+ ```
44
+ ::: ˆ
45
+ / An Opts block
46
+ ```opts
47
+ option1: 1
48
+ option2: 2
49
+ option3: 3
50
+ option4: 4
51
+ option5: 5
52
+ option6: 6
53
+ option7: 7
54
+ option8: 8
55
+ option9: 9
56
+ option10: 10
57
+ option11: 11
58
+ option12: 12
59
+ ```
60
+ ::: ˆ
61
+
62
+ | Variable| Value
63
+ | -| -
64
+ | VARIABLE1| ${VARIABLE1}
65
+ | VARIABLE2| ${VARIABLE2}
66
+
67
+ / A Vars block
68
+ ```vars
69
+ VARIABLE1: 1
70
+ VARIABLE2: 2
71
+ VARIABLE3: 3
72
+ VARIABLE4: 4
73
+ VARIABLE5: 5
74
+ VARIABLE6: 6
75
+ VARIABLE7: 7
76
+ VARIABLE8: 8
77
+ VARIABLE9: 9
78
+ VARIABLE10: 10
79
+ VARIABLE11: 11
80
+ VARIABLE12: 12
81
+ ```
82
+ ::: ˆ
83
+
84
+ | Variable| Value
85
+ | -| -
86
+ | VARIABLE| ${VARIABLE}
87
+
88
+ / An UX block
89
+ ```ux
90
+ init: false
91
+ echo:
92
+ VARIABLE1: 1
93
+ VARIABLE2: 2
94
+ VARIABLE3: 3
95
+ VARIABLE4: 4
96
+ VARIABLE5: 5
97
+ VARIABLE6: 6
98
+ VARIABLE7: 7
99
+ VARIABLE8: 8
100
+ VARIABLE9: 9
101
+ VARIABLE10: 10
102
+ VARIABLE11: 11
103
+ VARIABLE12: 12
104
+ name: VARIABLE1
105
+ ```
106
+ ::: ˆ
107
+ @import bats-document-configuration.md
108
+ ```opts :(document_opts)
109
+ screen_width: 48
110
+ select_page_height: 12
111
+ ```
@@ -0,0 +1,10 @@
1
+
2
+ | Option| Description| Value Length| Value| Default
3
+ | -| -| -| -| -
4
+ | screen_width| &{screen_width.description}| &{screen_width.length}| &{screen_width}| &{screen_width.default}
5
+ | table_center| &{table_center.description}| &{table_center.length}| &{table_center}| &{table_center.default}
6
+ @import bats-document-configuration.md
7
+ ```opts :(document_opts)
8
+ screen_width: 64
9
+ table_center: false
10
+ ```
@@ -116,6 +116,8 @@ class AnsiFormatter
116
116
  # @param default [String] Default color method to use if color_sym is not found in @options.
117
117
  # @return [String] The string with the applied color method.
118
118
  def string_send_color(string, color_sym, default: 'plain')
119
+ return string.to_s unless @options.fetch(:ansi_formatter_color, true)
120
+
119
121
  color_method = @options.fetch(color_sym, default).to_sym
120
122
  AnsiString.new(string.to_s).send(color_method)
121
123
  end
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env -S bundle exec ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ # a ruby class contains a list of objects
7
+ class BlockCache
8
+ attr_accessor :cache
9
+
10
+ def initialize(cache = [])
11
+ @cache = cache
12
+ end
13
+
14
+ # Find an object by id
15
+ def find_by_id(id)
16
+ @cache.find { |obj| obj.id == id }
17
+ end
18
+
19
+ # Get an existing object by id or create a new one
20
+ def get_or_create(id, new_obj_proc = nil)
21
+ obj = find_by_id(id)
22
+ return obj if obj
23
+
24
+ # Create a new object if not found
25
+ if block_given?
26
+ obj = yield(id)
27
+ elsif new_obj_proc
28
+ obj = new_obj_proc.call(id)
29
+ else
30
+ raise ArgumentError, 'Block or proc required to create new object'
31
+ end
32
+
33
+ @cache << obj
34
+ obj
35
+ end
36
+
37
+ # Add a new object to the cache
38
+ def add(obj)
39
+ raise ArgumentError,
40
+ 'Object must respond to id method' unless obj.respond_to?(:id)
41
+
42
+ # Replace existing object with same id
43
+ if (existing = find_by_id(obj.id))
44
+ @cache[@cache.index(existing)] = obj
45
+ else
46
+ @cache << obj
47
+ end
48
+ obj
49
+ end
50
+
51
+ # Remove an object from the cache by id
52
+ def remove(id)
53
+ @cache.delete_if { |obj| obj.id == id }
54
+ end
55
+
56
+ # Clear the entire cache
57
+ def clear
58
+ @cache = []
59
+ end
60
+
61
+ # Get the size of the cache
62
+ def size
63
+ @cache.size
64
+ end
65
+ end
66
+
67
+ return unless $PROGRAM_NAME == __FILE__
68
+
69
+ require 'bundler/setup'
70
+ Bundler.require(:default)
71
+
72
+ require 'minitest/autorun'
73
+ require 'mocha/minitest'
74
+
75
+ # require_relative 'ww'
76
+
77
+ # A simple dummy object that responds to #id
78
+ class DummyObject
79
+ attr_reader :id
80
+
81
+ def initialize(id)
82
+ @id = id
83
+ end
84
+ end
85
+
86
+ # Unit tests for the BlockCache class
87
+ #
88
+ # This test suite verifies correct behavior of:
89
+ # - initialization (with and without initial cache)
90
+ # - finding objects by id
91
+ # - getting or creating objects via block or proc
92
+ # - adding objects (including replacing existing ones)
93
+ # - removing objects by id
94
+ # - clearing the cache
95
+ # - reporting cache size
96
+ class TestBlockCache < Minitest::Test
97
+ def setup
98
+ @obj1 = DummyObject.new(1)
99
+ @obj2 = DummyObject.new(2)
100
+ @initial_cache = [@obj1]
101
+ @cache = BlockCache.new(@initial_cache.dup)
102
+ end
103
+
104
+ def test_initialize_defaults_to_empty_array
105
+ empty_cache = BlockCache.new
106
+ assert_equal [], empty_cache.cache
107
+ end
108
+
109
+ def test_initialize_with_provided_array
110
+ assert_equal @initial_cache, @cache.cache
111
+ end
112
+
113
+ def test_find_by_id_returns_matching_object
114
+ assert_equal @obj1, @cache.find_by_id(1)
115
+ end
116
+
117
+ def test_find_by_id_returns_nil_when_not_found
118
+ assert_nil @cache.find_by_id(999)
119
+ end
120
+
121
+ def test_get_or_create_returns_existing_object
122
+ found = @cache.get_or_create(1) { |id| DummyObject.new(id) }
123
+ assert_equal @obj1, found
124
+ assert_equal 1, @cache.size
125
+ end
126
+
127
+ def test_get_or_create_creates_with_block
128
+ new_obj = @cache.get_or_create(3) { |id| DummyObject.new(id) }
129
+ assert_instance_of DummyObject, new_obj
130
+ assert_equal 3, new_obj.id
131
+ assert_includes @cache.cache, new_obj
132
+ assert_equal 2, @cache.size
133
+ end
134
+
135
+ def test_get_or_create_creates_with_proc
136
+ factory = proc { |id| DummyObject.new(id * 10) }
137
+ new_obj = @cache.get_or_create(5, factory)
138
+ assert_instance_of DummyObject, new_obj
139
+ assert_equal 50, new_obj.id
140
+ assert_equal new_obj, @cache.find_by_id(50)
141
+ assert_equal 2, @cache.size
142
+ end
143
+
144
+ def test_get_or_create_raises_without_block_or_proc
145
+ assert_raises(ArgumentError) do
146
+ @cache.get_or_create(7)
147
+ end
148
+ end
149
+
150
+ def test_add_appends_new_object
151
+ result = @cache.add(@obj2)
152
+ assert_equal @obj2, result
153
+ assert_equal 2, @cache.size
154
+ assert_equal [@obj1, @obj2], @cache.cache
155
+ end
156
+
157
+ def test_add_replaces_existing_object_with_same_id
158
+ replacement = DummyObject.new(1)
159
+ result = @cache.add(replacement)
160
+ assert_equal replacement, result
161
+ assert_equal 1, @cache.size
162
+ assert_equal replacement, @cache.find_by_id(1)
163
+ end
164
+
165
+ def test_add_raises_for_object_without_id_method
166
+ invalid = Object.new
167
+ assert_raises(ArgumentError) do
168
+ @cache.add(invalid)
169
+ end
170
+ end
171
+
172
+ def test_remove_deletes_object_by_id
173
+ @cache.add(@obj2)
174
+ @cache.remove(1)
175
+ assert_nil @cache.find_by_id(1)
176
+ assert_equal 1, @cache.size
177
+ assert_equal [@obj2], @cache.cache
178
+ end
179
+
180
+ def test_remove_nonexistent_id_leaves_cache_unchanged
181
+ original = @cache.cache.dup
182
+ @cache.remove(999)
183
+ assert_equal original, @cache.cache
184
+ end
185
+
186
+ def test_clear_empties_the_cache
187
+ @cache.clear
188
+ assert_equal [], @cache.cache
189
+ assert_equal 0, @cache.size
190
+ end
191
+
192
+ def test_size_returns_number_of_cached_objects
193
+ assert_equal 1, @cache.size
194
+ @cache.add(@obj2)
195
+ assert_equal 2, @cache.size
196
+ end
197
+ end