holidays 9.1.0 → 9.1.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -1
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile +5 -0
  5. data/Makefile +14 -3
  6. data/README.md +1 -2
  7. data/Rakefile +19 -0
  8. data/doc/MAINTAINERS.md +2 -4
  9. data/holidays.gemspec +3 -3
  10. data/lib/generated_definitions/bg.rb +3 -2
  11. data/lib/generated_definitions/cy.rb +2 -1
  12. data/lib/generated_definitions/europe.rb +2 -1
  13. data/lib/holidays/definition/context/function_processor.rb +1 -1
  14. data/lib/holidays/definition/context/merger.rb +20 -4
  15. data/lib/holidays/definition/repository/custom_methods.rb +37 -9
  16. data/lib/holidays/finder/context/search.rb +43 -24
  17. data/lib/holidays/version.rb +1 -1
  18. data/lib/holidays.rb +9 -4
  19. data/test/coverage_report.rb +3 -5
  20. data/test/data/test_date_transform_conflict_region_1.yaml +14 -0
  21. data/test/data/test_date_transform_conflict_region_2.yaml +14 -0
  22. data/test/e2e/README.md +52 -0
  23. data/test/e2e/data/test_multiple_regions_with_conflicts_region_3.yaml +38 -0
  24. data/test/{integration → e2e}/test_holidays.rb +3 -2
  25. data/test/e2e/test_multiple_regions_with_conflict.rb +228 -0
  26. data/test/holidays/definition/context/test_function_processor.rb +2 -2
  27. data/test/holidays/definition/context/test_merger.rb +1 -1
  28. data/test/holidays/finder/context/test_search.rb +58 -0
  29. data/test/integration/README.md +45 -6
  30. data/test/integration/test_custom_holidays.rb +1 -1
  31. data/test/integration/test_custom_informal_holidays.rb +1 -1
  32. data/test/smoke/README.md +31 -0
  33. data/test/{integration → smoke}/test_available_regions.rb +0 -5
  34. data/test/smoke/test_smoke.rb +74 -0
  35. metadata +46 -34
  36. data/test/integration/test_multiple_regions_with_conflict.rb +0 -29
  37. /data/test/{data → e2e/data}/test_multiple_regions_with_conflicts_region_1.yaml +0 -0
  38. /data/test/{data → e2e/data}/test_multiple_regions_with_conflicts_region_2.yaml +0 -0
  39. /data/test/{integration → e2e}/test_all_regions.rb +0 -0
  40. /data/test/{integration → e2e}/test_any_holidays_during_work_week.rb +0 -0
  41. /data/test/{integration → e2e}/test_holidays_between.rb +0 -0
  42. /data/test/{integration → e2e}/test_multiple_regions.rb +0 -0
  43. /data/test/{integration → e2e}/test_nonstandard_regions.rb +0 -0
  44. /data/test/{data → integration/data}/test_custom_govt_holiday_defs.yaml +0 -0
  45. /data/test/{data → integration/data}/test_custom_informal_holidays_defs.yaml +0 -0
  46. /data/test/{data → integration/data}/test_multiple_custom_holiday_defs.yaml +0 -0
@@ -0,0 +1,228 @@
1
+ require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
2
+
3
+ # See https://github.com/holidays/holidays/issues/344 for more info on why
4
+ # these specific integration tests exist.
5
+ class MultipleRegionsWithConflictsTests < Test::Unit::TestCase
6
+ def test_corpus_christi_returns_correctly_for_co_even_if_br_is_loaded_first
7
+ result = Holidays.on(Date.new(2014, 6, 19), :br)
8
+ assert_equal 1, result.count
9
+ assert_equal 'Corpus Christi', result.first[:name]
10
+
11
+ result = Holidays.on(Date.new(2014, 6, 23), :co)
12
+ assert_equal 1, result.count
13
+ assert_equal 'Corpus Christi', result.first[:name]
14
+ end
15
+
16
+ def test_custom_loaded_region_returns_correct_value_with_function_modifier_conflict_even_if_conflict_definition_is_loaded_first
17
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
18
+ result = Holidays.on(Date.new(2019, 6, 20), :multiple_with_conflict_1)
19
+ assert_equal 1, result.count
20
+ assert_equal 'With Function Modifier', result.first[:name]
21
+
22
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
23
+ result = Holidays.on(Date.new(2019, 6, 24), :multiple_with_conflict_2)
24
+ assert_equal 1, result.count
25
+ assert_equal 'With Function Modifier', result.first[:name]
26
+
27
+ # Region 1 must still return the correct date even though region 2 was
28
+ # loaded afterwards with a different function modifier.
29
+ result = Holidays.on(Date.new(2019, 6, 20), :multiple_with_conflict_1)
30
+ assert_equal 1, result.count
31
+ assert_equal 'With Function Modifier', result.first[:name]
32
+ end
33
+
34
+ def test_custom_loaded_region_returns_correct_value_when_two_regions_share_function_name_but_have_different_logic
35
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
36
+
37
+ result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
38
+ assert_equal 1, result.count
39
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
40
+
41
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
42
+
43
+ result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_2)
44
+ assert_equal 1, result.count
45
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
46
+
47
+ # Region 1 must still return the correct date even though region 2 was
48
+ # loaded afterwards with the same function name but different logic.
49
+ result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
50
+ assert_equal 1, result.count
51
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
52
+ end
53
+
54
+ # Load region 2 first, then region 1, which is the opposite of the test above.
55
+ # The first-loaded method lands in @custom_methods; the second goes into
56
+ # @regional_overrides. This reversal exercises the fallback path for the
57
+ # first-loaded region rather than the override path.
58
+ def test_custom_loaded_region_returns_correct_value_when_load_order_is_reversed
59
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
60
+
61
+ result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_2)
62
+ assert_equal 1, result.count
63
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
64
+
65
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
66
+
67
+ result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
68
+ assert_equal 1, result.count
69
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
70
+
71
+ # Region 2 must still return the correct date even though region 1 was
72
+ # loaded afterwards with the same function name but different logic.
73
+ result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_2)
74
+ assert_equal 1, result.count
75
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
76
+ end
77
+
78
+ # Both regions define holidays with the same custom function name but
79
+ # different Ruby logic and different holiday names. Because the names differ,
80
+ # holidays_by_month keeps them as separate entries (no region merge). Conflict
81
+ # resolution must still route each holiday to its own function implementation.
82
+ def test_custom_loaded_region_returns_correct_value_when_function_name_is_shared_but_holiday_names_differ
83
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
84
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
85
+
86
+ result = Holidays.on(Date.new(2019, 9, 15), :multiple_with_conflict_1)
87
+ assert_equal 1, result.count
88
+ assert_equal 'With Function Only Same Function Name - Region 1', result.first[:name]
89
+
90
+ result = Holidays.on(Date.new(2019, 11, 15), :multiple_with_conflict_2)
91
+ assert_equal 1, result.count
92
+ assert_equal 'With Function Only Same Function Name - Region 2', result.first[:name]
93
+ end
94
+
95
+ # When two regions are queried together and both have a holiday that uses the
96
+ # same function name but different logic, the search must evaluate the function
97
+ # independently for each queried region and return the union of all matches.
98
+ def test_simultaneous_multi_region_query_evaluates_each_region_function_independently
99
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
100
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
101
+
102
+ result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_1, :multiple_with_conflict_2)
103
+ assert_equal 1, result.count
104
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
105
+
106
+ result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1, :multiple_with_conflict_2)
107
+ assert_equal 1, result.count
108
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
109
+ end
110
+
111
+ # https://www.github.com/holidays/holidays/issues/344 scenario: two regions
112
+ # share a function name but each implements it differently, so a single between() call
113
+ # spanning both result dates must return one holiday entry per region.
114
+ #
115
+ # Region 1's function returns Sept 1; region 2's function returns Nov 1.
116
+ # Querying Sept 1 to Nov 1 with both regions should produce two separate
117
+ # 'With Function Only Same Function Name' entries, one per region.
118
+ def test_simultaneous_multi_region_query_returns_one_result_per_region_in_a_single_call
119
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
120
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
121
+
122
+ result = Holidays.between(
123
+ Date.new(2019, 9, 1),
124
+ Date.new(2019, 11, 1),
125
+ :multiple_with_conflict_1,
126
+ :multiple_with_conflict_2,
127
+ )
128
+
129
+ matching = result.select { |h| h[:name] == 'With Function Only Same Function Name' }
130
+ assert_equal 2, matching.count
131
+ assert matching.any? { |h| h[:date] == Date.new(2019, 9, 1) }, 'expected region 1 result on Sept 1'
132
+ assert matching.any? { |h| h[:date] == Date.new(2019, 11, 1) }, 'expected region 2 result on Nov 1'
133
+ end
134
+
135
+ # Tests the conflict resolution path for functions that take a date argument
136
+ # and return a shifted date, as opposed to functions that return a fixed date
137
+ # for a given year. This is the canonical scenario from https://www.github.com/holidays/holidays/issues/344:
138
+ # two regions define the same function name with different shift logic.
139
+ #
140
+ # Region 1: shifts Jan 1 to the nearest upcoming Saturday.
141
+ # Region 2: shifts Jan 1 to the nearest upcoming Sunday.
142
+ #
143
+ # Jan 1, 2025 is a Wednesday, so:
144
+ # Region 1 -> Jan 4, 2025 (Saturday, +3 days)
145
+ # Region 2 -> Jan 5, 2025 (Sunday, +4 days)
146
+ def test_date_transforming_functions_with_conflicting_logic_are_each_evaluated_independently
147
+ Holidays.load_custom('test/data/test_date_transform_conflict_region_1.yaml')
148
+ Holidays.load_custom('test/data/test_date_transform_conflict_region_2.yaml')
149
+
150
+ # Single-region: each region resolves to its own shifted date.
151
+ result = Holidays.on(Date.new(2025, 1, 4), :date_transform_conflict_1)
152
+ assert_equal 1, result.count
153
+ assert_equal 'Weekend Holiday', result.first[:name]
154
+
155
+ result = Holidays.on(Date.new(2025, 1, 5), :date_transform_conflict_2)
156
+ assert_equal 1, result.count
157
+ assert_equal 'Weekend Holiday', result.first[:name]
158
+
159
+ # Multi-region: a single call spanning both shifted dates must return one
160
+ # result per region, each evaluated with its own function logic.
161
+ result = Holidays.between(
162
+ Date.new(2025, 1, 4),
163
+ Date.new(2025, 1, 5),
164
+ :date_transform_conflict_1,
165
+ :date_transform_conflict_2,
166
+ )
167
+
168
+ matching = result.select { |h| h[:name] == 'Weekend Holiday' }
169
+ assert_equal 2, matching.count
170
+ assert matching.any? { |h| h[:date] == Date.new(2025, 1, 4) }, 'expected region 1 result on Jan 4 (Saturday)'
171
+ assert matching.any? { |h| h[:date] == Date.new(2025, 1, 5) }, 'expected region 2 result on Jan 5 (Sunday)'
172
+ end
173
+
174
+ # Three regions all share the same function name but each implements it with
175
+ # different logic (region 1 -> Sept 1, region 2 -> Nov 1, region 3 -> Mar 1).
176
+ # After all three are loaded, every region must still resolve to its own date.
177
+ def test_three_regions_with_same_function_name_each_return_correct_result
178
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
179
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
180
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_3.yaml')
181
+
182
+ result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
183
+ assert_equal 1, result.count
184
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
185
+
186
+ result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_2)
187
+ assert_equal 1, result.count
188
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
189
+
190
+ result = Holidays.on(Date.new(2019, 3, 1), :multiple_with_conflict_3)
191
+ assert_equal 1, result.count
192
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
193
+ end
194
+
195
+ # A single between() call across all three regions must return one result per
196
+ # region, each evaluated with its own function implementation.
197
+ def test_simultaneous_three_region_query_returns_one_result_per_region
198
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
199
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
200
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_3.yaml')
201
+
202
+ result = Holidays.between(
203
+ Date.new(2019, 3, 1),
204
+ Date.new(2019, 11, 1),
205
+ :multiple_with_conflict_1,
206
+ :multiple_with_conflict_2,
207
+ :multiple_with_conflict_3,
208
+ )
209
+
210
+ matching = result.select { |h| h[:name] == 'With Function Only Same Function Name' }
211
+ assert_equal 3, matching.count
212
+ assert matching.any? { |h| h[:date] == Date.new(2019, 3, 1) }, 'expected region 3 result on Mar 1'
213
+ assert matching.any? { |h| h[:date] == Date.new(2019, 9, 1) }, 'expected region 1 result on Sept 1'
214
+ assert matching.any? { |h| h[:date] == Date.new(2019, 11, 1) }, 'expected region 2 result on Nov 1'
215
+ end
216
+
217
+ # Verifies the safe-overwrite path in CustomMethods#add: when the same
218
+ # source is added again the method is simply overwritten and the holiday
219
+ # definition repo de-duplicates via uniq!, so exactly one result is returned.
220
+ def test_loading_the_same_custom_file_twice_does_not_duplicate_or_break_results
221
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
222
+ Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
223
+
224
+ result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
225
+ assert_equal 1, result.count
226
+ assert_equal 'With Function Only Same Function Name', result.first[:name]
227
+ end
228
+ end
@@ -19,7 +19,7 @@ class FunctionProcessorTests < Test::Unit::TestCase
19
19
 
20
20
  @custom_func = mock()
21
21
 
22
- @custom_methods_repo.expects(:find).at_most_once.with(@func_id).returns(@custom_func)
22
+ @custom_methods_repo.expects(:find).at_most_once.with(@func_id, @region).returns(@custom_func)
23
23
  @proc_result_cache_repo.expects(:lookup).at_most_once.with(@custom_func, @year).returns(Date.civil(@year, @month, @day))
24
24
 
25
25
  @subject = Holidays::Definition::Context::FunctionProcessor.new(
@@ -46,7 +46,7 @@ class FunctionProcessorTests < Test::Unit::TestCase
46
46
 
47
47
  def test_unknown_function_id_returns_error
48
48
  bad_id = "some-bad-id"
49
- @custom_methods_repo.expects(:find).at_most_once.with(bad_id).returns(nil)
49
+ @custom_methods_repo.expects(:find).at_most_once.with(bad_id, @region).returns(nil)
50
50
 
51
51
  assert_raises Holidays::FunctionNotFound do
52
52
  @subject.call(@input, bad_id, @func_args, @func_modifier)
@@ -18,7 +18,7 @@ class MergerTests < Test::Unit::TestCase
18
18
  def test_repos_are_called_to_add_regions_and_holidays
19
19
  @holidays_repo.expects(:add).with(@target_holidays)
20
20
  @regions_repo.expects(:add).with(@target_regions)
21
- @custom_methods_repo.expects(:add).with(@target_custom_methods)
21
+ @custom_methods_repo.expects(:add).with(@target_custom_methods, {}, {})
22
22
 
23
23
  @subject.call(@target_regions, @target_holidays, @target_custom_methods)
24
24
  end
@@ -229,4 +229,62 @@ class FinderSearchTests < Test::Unit::TestCase
229
229
  @subject.call(@dates_driver, @regions, @options)
230
230
  )
231
231
  end
232
+
233
+ def test_nil_returned_from_one_region_function_does_not_suppress_valid_result_from_other_region
234
+ two_regions = [:r1, :r2]
235
+
236
+ @holidays_by_month_repo.expects(:find_by_month).at_most_once.returns(
237
+ [{ mday: 1, name: "Test", regions: two_regions, function: "func-id", function_arguments: [:year], function_modifier: nil }]
238
+ )
239
+ @in_region_rule.expects(:call).with(two_regions, two_regions).returns(true)
240
+
241
+ @custom_method_processor.expects(:call).with(
242
+ { year: 2015, month: 1, day: 1, region: :r1 }, "func-id", [:year], nil,
243
+ ).returns(nil)
244
+
245
+ @custom_method_processor.expects(:call).with(
246
+ { year: 2015, month: 1, day: 1, region: :r2 }, "func-id", [:year], nil,
247
+ ).returns(Date.civil(2015, 6, 15))
248
+
249
+ result = @subject.call(@dates_driver, two_regions, [])
250
+ assert_equal 1, result.count
251
+ assert_equal Date.civil(2015, 6, 15), result.first[:date]
252
+ end
253
+
254
+ def test_conflicting_function_in_two_regions_evaluates_each_independently
255
+ two_regions = [:r1, :r2]
256
+
257
+ @holidays_by_month_repo.expects(:find_by_month).at_most_once.returns(
258
+ [{ mday: 1, name: "Test", regions: two_regions, function: "func-id", function_arguments: [:year], function_modifier: nil }]
259
+ )
260
+ @in_region_rule.expects(:call).with(two_regions, two_regions).returns(true)
261
+
262
+ @custom_method_processor.expects(:call).with(
263
+ { year: 2015, month: 1, day: 1, region: :r1 }, "func-id", [:year], nil,
264
+ ).returns(Date.civil(2015, 3, 10))
265
+
266
+ @custom_method_processor.expects(:call).with(
267
+ { year: 2015, month: 1, day: 1, region: :r2 }, "func-id", [:year], nil,
268
+ ).returns(Date.civil(2015, 6, 15))
269
+
270
+ result = @subject.call(@dates_driver, two_regions, [])
271
+ assert_equal 2, result.count
272
+ assert result.any? { |h| h[:date] == Date.civil(2015, 3, 10) }
273
+ assert result.any? { |h| h[:date] == Date.civil(2015, 6, 15) }
274
+ end
275
+
276
+ def test_any_region_query_with_function_evaluates_function_exactly_once
277
+ @holidays_by_month_repo.expects(:find_by_month).at_most_once.returns(
278
+ [{ mday: 1, name: "Test", regions: [:r1], function: "func-id", function_arguments: [:year], function_modifier: nil }]
279
+ )
280
+ @in_region_rule.expects(:call).with([:any], [:r1]).returns(true)
281
+
282
+ @custom_method_processor.expects(:call).with(
283
+ { year: 2015, month: 1, day: 1, region: :any }, "func-id", [:year], nil,
284
+ ).returns(Date.civil(2015, 3, 10))
285
+
286
+ result = @subject.call(@dates_driver, [:any], [])
287
+ assert_equal 1, result.count
288
+ assert_equal Date.civil(2015, 3, 10), result.first[:date]
289
+ end
232
290
  end
@@ -1,9 +1,48 @@
1
1
  # Integration tests
2
2
 
3
- These tests are dependent on the files in /definitions (and, by proxy, /lib/generated_definitions).
4
- It is possible that these tests will break because of 'unrelated' definition changes. The code
5
- behind these changes could still be good but since the definitions changed we could see failures.
3
+ True gem integration tests that verify how the gem's components work together
4
+ (loader, calculator, observed-date logic, sub-region inheritance, caching, etc.)
5
+ using **local fixture YAMLs**. Integration-only fixtures live in
6
+ `test/integration/data/`. Fixtures shared with other test categories live in
7
+ `test/data/`.
6
8
 
7
- These are not unit tests. This is not testing whether specific internal code is working. These are
8
- tests from the consumer perspective. You must recognize that this could fail because of code
9
- changes unrelated to definition changes.
9
+ ## Purpose
10
+
11
+ These tests pin specific inputs (the fixture YAMLs) and assert specific outputs,
12
+ so they verify gem behavior independently of whatever the upstream definitions
13
+ repo currently ships. They do not break when upstream definitions change.
14
+
15
+ ## Integration tests vs. unit tests
16
+
17
+ Unit tests live alongside the code they cover and verify a single file or
18
+ class in isolation. They are the right place for testing the internals of one
19
+ specific component (e.g. a date calculator, a custom method validator) with
20
+ mocks and minimal setup.
21
+
22
+ Integration tests in this directory are the opposite: they wire multiple gem
23
+ components together and verify that they cooperate correctly across the full
24
+ parse → calculate → return pipeline. If you are testing one file's internal
25
+ behavior, write a unit test. If you are testing how several pieces of the gem
26
+ behave together against a known input, write an integration test here.
27
+
28
+ ## What belongs here
29
+
30
+ - Tests that exercise gem internals end-to-end (parsing, calculation, output)
31
+ using controlled inputs from `test/integration/data/` (or `test/data/` for
32
+ fixtures shared with other test categories).
33
+ - Tests for custom-method evaluation, year-range handling, informal flag
34
+ filtering, observed-date shifting, sub-region inheritance, etc., using fixtures.
35
+
36
+ ## What does NOT belong here
37
+
38
+ - Any test that loads or asserts against real definitions in `/definitions`.
39
+ Those belong in `e2e/`.
40
+ - Pure structural checks that don't assert specific behavior. Those belong in
41
+ `smoke/`.
42
+ - Tests of a single file or class in isolation. Those are unit tests and belong
43
+ next to the code they cover.
44
+
45
+ The advantage of fixture-based tests: a definitions change cannot break them.
46
+ If you find yourself writing a test that needs to load `:ca` or `:us` directly,
47
+ ask whether the same behavior could be tested with a small fixture YAML, and
48
+ if so, write the fixture instead.
@@ -31,7 +31,7 @@ class CustomHolidaysTest < Test::Unit::TestCase
31
31
  Holidays.on(Date.civil(2013,3,1), :custom_multiple_files_govt)
32
32
  end
33
33
 
34
- Holidays.load_custom('test/data/test_multiple_custom_holiday_defs.yaml', 'test/data/test_custom_govt_holiday_defs.yaml')
34
+ Holidays.load_custom('test/integration/data/test_multiple_custom_holiday_defs.yaml', 'test/integration/data/test_custom_govt_holiday_defs.yaml')
35
35
 
36
36
  assert_not_equal [], Holidays.on(Date.civil(2013,10,5), :custom_multiple_files)
37
37
  assert_not_equal [], Holidays.on(Date.civil(2013,3,1), :custom_multiple_files)
@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
3
3
  class CustomHolidaysTest < Test::Unit::TestCase
4
4
 
5
5
  def test_custom_region_informal_day_parsing
6
- Holidays.load_custom('test/data/test_custom_informal_holidays_defs.yaml')
6
+ Holidays.load_custom('test/integration/data/test_custom_informal_holidays_defs.yaml')
7
7
 
8
8
  assert_not_equal [], Holidays.on(Date.new(2018,1,1), :custom_informal, :informal)
9
9
  assert_equal [], Holidays.on(Date.new(2018,1,1), :custom_informal, :observed)
@@ -0,0 +1,31 @@
1
+ # Smoke tests
2
+
3
+ Structural-only checks across every region loaded from `/definitions`. These tests
4
+ verify that the gem can load and process all definitions without raising errors.
5
+ They make no assertions about specific holiday names, dates, or counts.
6
+
7
+ ## Purpose
8
+
9
+ These tests are run from the upstream `holidays/definitions` repo's CI as the
10
+ downstream check. The contract is narrow on purpose: a definitions change should
11
+ only fail this suite if it actually breaks the gem's ability to load or process
12
+ definitions, never because of intentional content changes (renaming a holiday,
13
+ adding a region, shifting a date, etc.).
14
+
15
+ ## What belongs here
16
+
17
+ - Tests that exercise the gem's API surface (`Holidays.on`, `Holidays.between`,
18
+ flags like `:observed` and `:informal`) across all regions and assert only
19
+ that nothing raises.
20
+ - Tests that verify the *shape* of return values (e.g. `available_regions` is
21
+ an Array of Symbols).
22
+ - Cross-region invariants that hold regardless of content (e.g. `:any` count is
23
+ ≥ each individual region's count).
24
+
25
+ ## What does NOT belong here
26
+
27
+ - Any assertion about a specific holiday name, date, or count.
28
+ - Any check that depends on the contents of a particular region's YAML.
29
+
30
+ If a check would fail when someone legitimately renames "Labour Day" to
31
+ "Labor Day" in `ca.yaml`, it does not belong in `smoke/`. It belongs in `e2e/`.
@@ -15,9 +15,4 @@ class AvailableRegionsTests < Test::Unit::TestCase
15
15
  end
16
16
  end
17
17
 
18
- # This test might fail if we add new regions. Since this is an integration test
19
- # I am fine with that!
20
- def test_available_regions_returns_correct_number_of_regions
21
- assert_equal 286, Holidays.available_regions.count
22
- end
23
18
  end
@@ -0,0 +1,74 @@
1
+ require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
2
+
3
+ class SmokeTests < Test::Unit::TestCase
4
+ SAMPLE_DATES = [
5
+ Date.civil(2023, 1, 1),
6
+ Date.civil(2023, 7, 4),
7
+ Date.civil(2023, 12, 25),
8
+ ]
9
+
10
+ def setup
11
+ Holidays.load_all
12
+ @regions = Holidays.available_regions
13
+ end
14
+
15
+ def test_all_regions_load_without_error
16
+ assert @regions.is_a?(Array)
17
+ assert @regions.count > 0
18
+ assert @regions.all? { |r| r.is_a?(Symbol) }
19
+ end
20
+
21
+ def test_holidays_on_does_not_raise_for_any_region
22
+ @regions.each do |region|
23
+ SAMPLE_DATES.each do |date|
24
+ assert_nothing_raised("Holidays.on raised for :#{region} on #{date}") do
25
+ result = Holidays.on(date, region)
26
+ assert result.is_a?(Array)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def test_holidays_between_does_not_raise_for_any_region
33
+ start_date = Date.civil(2023, 1, 1)
34
+ end_date = Date.civil(2023, 12, 31)
35
+
36
+ @regions.each do |region|
37
+ assert_nothing_raised("Holidays.between raised for :#{region}") do
38
+ result = Holidays.between(start_date, end_date, region)
39
+ assert result.is_a?(Array)
40
+ end
41
+ end
42
+ end
43
+
44
+ def test_observed_flag_does_not_raise_for_any_region
45
+ @regions.each do |region|
46
+ SAMPLE_DATES.each do |date|
47
+ assert_nothing_raised("Holidays.on with :observed raised for :#{region} on #{date}") do
48
+ Holidays.on(date, region, :observed)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def test_informal_flag_does_not_raise_for_any_region
55
+ @regions.each do |region|
56
+ SAMPLE_DATES.each do |date|
57
+ assert_nothing_raised("Holidays.on with :informal raised for :#{region} on #{date}") do
58
+ Holidays.on(date, region, :informal)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def test_any_region_count_meets_or_exceeds_each_individual_region
65
+ start_date = Date.civil(2023, 1, 1)
66
+ end_date = Date.civil(2023, 12, 31)
67
+ any_count = Holidays.between(start_date, end_date, :any).count
68
+
69
+ @regions.each do |region|
70
+ region_count = Holidays.between(start_date, end_date, region).count
71
+ assert any_count >= region_count, ":any count (#{any_count}) is less than :#{region} count (#{region_count})"
72
+ end
73
+ end
74
+ end