From 09bddb988ba7cd6430984eb89cfcfd7d4cf6ce30 Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Wed, 18 Mar 2026 21:54:14 -0700 Subject: [PATCH 01/12] test(conformance): migrate node suites to shared fixtures --- src/__tests__/calendar.spec.ts | 51 -- src/__tests__/characterCodesToAscii.spec.ts | 19 - src/__tests__/characterCodesToString.spec.ts | 67 -- src/__tests__/classic.spec.ts | 224 ------ src/__tests__/conformance/calendar.spec.ts | 35 + .../conformance/characterCodesToAscii.spec.ts | 14 + .../characterCodesToString.spec.ts | 15 + src/__tests__/conformance/classic.spec.ts | 16 + .../conformance/hasSpecialCharacters.spec.ts | 12 + .../conformance/parseComponent.spec.ts | 34 + .../sanitizeSpecialCharacters.spec.ts | 12 + src/__tests__/conformance/support.ts | 180 +++++ src/__tests__/conformance/vbml.spec.ts | 9 + src/__tests__/hasSpecialCharacters.spec.ts | 73 -- src/__tests__/parseComponent.spec.ts | 481 ------------ src/__tests__/randomColors.spec.ts | 12 - .../sanitizeSpecialCharacters.spec.ts | 145 ---- src/__tests__/vbml.spec.ts | 700 ------------------ test/README.md | 17 + .../creates_calendar_with_four_weeks.json | 10 + ...s_not_highlight_days_outside_of_month.json | 10 + ...ate_header_when_last_week_has_one_day.json | 10 + .../converts_color_codes.json | 3 + .../handles_multiple_rows.json | 3 + .../spaces_out_letters.json | 3 + .../converts_two_line_sentence.json | 3 + .../converts_word_to_string.json | 3 + ...nsert_line_break_when_first_word_fits.json | 3 + .../handles_breaks.json | 3 + .../handles_line_breaks.json | 3 + test/expected/classic/converts_ae_umlaut.json | 10 + .../classic/converts_double_newline.json | 10 + .../converts_embedded_char_code_string.json | 10 + .../classic/converts_emoji_colors.json | 10 + .../classic/converts_hyphen_string.json | 10 + .../converts_long_string_with_digits.json | 10 + .../classic/converts_longer_string.json | 10 + .../classic/converts_single_char_code.json | 10 + .../classic/converts_single_newline.json | 10 + .../classic/converts_special_characters.json | 10 + .../converts_string_to_classic_board.json | 10 + .../classic/preserves_triple_spaces.json | 10 + .../classic/respects_double_spaces.json | 10 + .../returns_empty_board_for_empty_string.json | 10 + .../excludes_black_color_swatch.json | 3 + .../excludes_ios_double_quote.json | 3 + .../excludes_ios_single_quote.json | 3 + .../excludes_newlines.json | 3 + .../excludes_orange_color_swatch.json | 3 + .../excludes_white_color_swatch.json | 3 + .../includes_fractions.json | 3 + .../returns_false_for_empty_string.json | 3 + .../returns_false_for_lowercase_alphabet.json | 3 + .../returns_false_for_numbers.json | 3 + .../returns_false_for_supported_symbols.json | 3 + .../returns_false_for_uppercase_alphabet.json | 3 + ...mixed_special_and_standard_characters.json | 3 + .../returns_true_for_special_character.json | 3 + .../parseComponent/adds_extra_spaces.json | 5 + .../parseComponent/adds_template_props.json | 5 + .../allows_array_iteration.json | 5 + .../allows_conditions_when_false.json | 5 + .../allows_conditions_when_true.json | 5 + .../parseComponent/allows_newlines.json | 6 + .../allows_newlines_after_spaces.json | 6 + .../allows_newlines_before_spaces.json | 6 + .../automatically_breaks_line.json | 6 + .../breaks_on_lines_with_character_codes.json | 6 + ...s_emoji_characters_to_character_codes.json | 5 + .../does_not_break_when_unnecessary.json | 7 + .../formats_longer_message_centered.json | 6 + .../formats_longer_plain_text.json | 6 + .../parseComponent/formats_plain_text.json | 5 + .../horizontally_aligns_center.json | 5 + .../horizontally_aligns_justified.json | 10 + .../horizontally_aligns_right.json | 5 + ...ontally_and_vertically_aligned_center.json | 7 + .../justified_long_complex_message.json | 10 + .../justified_when_flowing_to_next_line.json | 10 + .../justified_when_full_line_covered.json | 10 + .../parses_absolute_component.json | 10 + .../parses_character_codes.json | 5 + .../parseComponent/parses_raw_component.json | 6 + .../parses_two_digit_character_codes.json | 5 + .../parseComponent/splits_long_words.json | 6 + .../throws_for_invalid_character_codes.json | 5 + .../vertically_aligns_bottom.json | 8 + .../vertically_aligns_center.json | 7 + ...ertically_aligns_center_multiple_rows.json | 9 + ...er_sticks_to_top_when_no_even_padding.json | 10 + .../accepts_whitespace_after_heart.json | 3 + .../converts_cedilla.json | 3 + .../converts_ellipsis.json | 3 + .../converts_en_dash.json | 3 + .../converts_f_hook.json | 3 + .../converts_fraction_slash.json | 3 + .../converts_german_lowercase_ae.json | 3 + .../converts_german_lowercase_oe.json | 3 + .../converts_german_lowercase_ue.json | 3 + .../converts_german_uppercase_ae.json | 3 + .../converts_german_uppercase_oe.json | 3 + .../converts_german_uppercase_ue.json | 3 + .../converts_ligature_ae.json | 3 + .../converts_ligature_oe.json | 3 + .../converts_micro_sign_to_space.json | 3 + .../converts_scandinavian_a_ring.json | 3 + .../converts_scandinavian_o_stroke.json | 3 + .../converts_sharp_s.json | 3 + .../converts_sharp_s_in_fussball.json | 3 + .../converts_sharp_s_in_gross.json | 3 + .../converts_sharp_s_in_weiss.json | 3 + ...ported_sequenced_emojis_to_whitespace.json | 3 + .../does_not_modify_plain_text.json | 3 + .../does_not_replace_vestaboard_heart.json | 3 + .../handles_german_sharp_s_in_context.json | 3 + ...s_german_text_with_umlauts_in_context.json | 3 + ...es_heart_emoji_and_unsupported_emojis.json | 3 + .../handles_mixed_special_characters.json | 3 + ..._multiple_special_characters_together.json | 3 + .../handles_sentence.json | 3 + ...eserves_space_between_heart_and_emoji.json | 3 + ...s_space_between_heart_and_latin_glyph.json | 3 + .../preserves_spaces_between_hearts.json | 3 + .../replaces_accented_character.json | 3 + ...es_fractions_with_multiple_characters.json | 3 + ...s_variation_selector_from_heart_emoji.json | 3 + ...riation_selector_from_unicode_literal.json | 3 + .../flows_third_component_to_next_line.json | 6 + test/expected/vbml/formats_ae_umlaut.json | 5 + .../vbml/justifies_content_vertically.json | 9 + ...tifies_content_vertically_three_chars.json | 9 + ...ute_components_by_relative_components.json | 26 + ...ith_raw_components_for_mountain_clock.json | 10 + ...uts_absolute_over_relative_components.json | 26 + ...ver_relative_components_standard_size.json | 10 + ...outs_calendar_component_for_christmas.json | 10 + ...youts_calendar_component_on_the_right.json | 10 + ...endar_component_with_other_components.json | 10 + .../vbml/layouts_components_side_by_side.json | 5 + .../vbml/layouts_components_vertically.json | 6 + ...layouts_minimalist_calendar_component.json | 10 + .../expected/vbml/layouts_raw_components.json | 10 + .../parses_single_component_on_a_board.json | 5 + .../vbml/respects_double_returns.json | 7 + .../vbml/respects_triple_returns.json | 8 + .../creates_calendar_with_four_weeks.json | 5 + ...s_not_highlight_days_outside_of_month.json | 9 + ...ate_header_when_last_week_has_one_day.json | 5 + .../converts_color_codes.json | 14 + .../handles_multiple_rows.json | 12 + .../spaces_out_letters.json | 8 + .../converts_two_line_sentence.json | 148 ++++ .../converts_word_to_string.json | 8 + ...nsert_line_break_when_first_word_fits.json | 15 + .../handles_breaks.json | 52 ++ .../handles_line_breaks.json | 21 + test/input/classic/converts_ae_umlaut.json | 3 + .../classic/converts_double_newline.json | 3 + .../converts_embedded_char_code_string.json | 6 + test/input/classic/converts_emoji_colors.json | 3 + .../input/classic/converts_hyphen_string.json | 3 + .../converts_long_string_with_digits.json | 3 + .../input/classic/converts_longer_string.json | 3 + .../classic/converts_single_char_code.json | 3 + .../classic/converts_single_newline.json | 3 + .../classic/converts_special_characters.json | 3 + .../converts_string_to_classic_board.json | 3 + .../classic/preserves_triple_spaces.json | 6 + .../input/classic/respects_double_spaces.json | 6 + .../returns_empty_board_for_empty_string.json | 3 + .../excludes_black_color_swatch.json | 3 + .../excludes_ios_double_quote.json | 3 + .../excludes_ios_single_quote.json | 3 + .../excludes_newlines.json | 3 + .../excludes_orange_color_swatch.json | 3 + .../excludes_white_color_swatch.json | 3 + .../includes_fractions.json | 3 + .../returns_false_for_empty_string.json | 3 + .../returns_false_for_lowercase_alphabet.json | 3 + .../returns_false_for_numbers.json | 3 + .../returns_false_for_supported_symbols.json | 3 + .../returns_false_for_uppercase_alphabet.json | 3 + ...mixed_special_and_standard_characters.json | 3 + .../returns_true_for_special_character.json | 3 + .../parseComponent/adds_extra_spaces.json | 7 + .../parseComponent/adds_template_props.json | 10 + .../allows_array_iteration.json | 14 + .../allows_conditions_when_false.json | 10 + .../allows_conditions_when_true.json | 10 + .../input/parseComponent/allows_newlines.json | 7 + .../allows_newlines_after_spaces.json | 7 + .../allows_newlines_before_spaces.json | 10 + .../automatically_breaks_line.json | 7 + .../breaks_on_lines_with_character_codes.json | 11 + ...s_emoji_characters_to_character_codes.json | 7 + .../does_not_break_when_unnecessary.json | 7 + .../formats_longer_message_centered.json | 10 + .../formats_longer_plain_text.json | 7 + .../parseComponent/formats_plain_text.json | 7 + .../horizontally_aligns_center.json | 10 + .../horizontally_aligns_justified.json | 11 + .../horizontally_aligns_right.json | 10 + ...ontally_and_vertically_aligned_center.json | 11 + .../justified_long_complex_message.json | 11 + .../justified_when_flowing_to_next_line.json | 11 + .../justified_when_full_line_covered.json | 11 + .../parses_absolute_component.json | 16 + .../parses_character_codes.json | 11 + .../parseComponent/parses_raw_component.json | 16 + .../parses_two_digit_character_codes.json | 11 + .../parseComponent/splits_long_words.json | 7 + .../throws_for_invalid_character_codes.json | 11 + .../vertically_aligns_bottom.json | 10 + .../vertically_aligns_center.json | 10 + ...ertically_aligns_center_multiple_rows.json | 10 + ...er_sticks_to_top_when_no_even_padding.json | 10 + .../accepts_whitespace_after_heart.json | 3 + .../converts_cedilla.json | 3 + .../converts_ellipsis.json | 3 + .../converts_en_dash.json | 3 + .../converts_f_hook.json | 3 + .../converts_fraction_slash.json | 3 + .../converts_german_lowercase_ae.json | 3 + .../converts_german_lowercase_oe.json | 3 + .../converts_german_lowercase_ue.json | 3 + .../converts_german_uppercase_ae.json | 3 + .../converts_german_uppercase_oe.json | 3 + .../converts_german_uppercase_ue.json | 3 + .../converts_ligature_ae.json | 3 + .../converts_ligature_oe.json | 3 + .../converts_micro_sign_to_space.json | 3 + .../converts_scandinavian_a_ring.json | 3 + .../converts_scandinavian_o_stroke.json | 3 + .../converts_sharp_s.json | 3 + .../converts_sharp_s_in_fussball.json | 3 + .../converts_sharp_s_in_gross.json | 3 + .../converts_sharp_s_in_weiss.json | 3 + ...ported_sequenced_emojis_to_whitespace.json | 3 + .../does_not_modify_plain_text.json | 3 + .../does_not_replace_vestaboard_heart.json | 3 + .../handles_german_sharp_s_in_context.json | 3 + ...s_german_text_with_umlauts_in_context.json | 3 + ...es_heart_emoji_and_unsupported_emojis.json | 3 + .../handles_mixed_special_characters.json | 3 + ..._multiple_special_characters_together.json | 3 + .../handles_sentence.json | 3 + ...eserves_space_between_heart_and_emoji.json | 3 + ...s_space_between_heart_and_latin_glyph.json | 3 + .../preserves_spaces_between_hearts.json | 3 + .../replaces_accented_character.json | 3 + ...es_fractions_with_multiple_characters.json | 3 + ...s_variation_selector_from_heart_emoji.json | 3 + ...riation_selector_from_unicode_literal.json | 3 + .../flows_third_component_to_next_line.json | 29 + test/input/vbml/formats_ae_umlaut.json | 15 + .../vbml/justifies_content_vertically.json | 16 + ...tifies_content_vertically_three_chars.json | 16 + ...ute_components_by_relative_components.json | 30 + ...ith_raw_components_for_mountain_clock.json | 170 +++++ ...uts_absolute_over_relative_components.json | 30 + ...ver_relative_components_standard_size.json | 30 + ...outs_calendar_component_for_christmas.json | 24 + ...youts_calendar_component_on_the_right.json | 35 + ...endar_component_with_other_components.json | 40 + .../vbml/layouts_components_side_by_side.json | 22 + .../vbml/layouts_components_vertically.json | 22 + ...layouts_minimalist_calendar_component.json | 27 + test/input/vbml/layouts_raw_components.json | 17 + .../parses_single_component_on_a_board.json | 11 + test/input/vbml/respects_double_returns.json | 15 + test/input/vbml/respects_triple_returns.json | 15 + ...ute_components_by_relative_components.json | 27 + ...uts_absolute_over_relative_components.json | 27 + .../classic/converts_special_characters.json | 11 + .../excludes_ios_single_quote.json | 4 + ...ute_components_by_relative_components.json | 27 + ...uts_absolute_over_relative_components.json | 27 + ...outs_calendar_component_for_christmas.json | 6 + ...youts_calendar_component_on_the_right.json | 6 + ...endar_component_with_other_components.json | 6 + ...layouts_minimalist_calendar_component.json | 6 + 281 files changed, 2615 insertions(+), 1772 deletions(-) delete mode 100644 src/__tests__/calendar.spec.ts delete mode 100644 src/__tests__/characterCodesToAscii.spec.ts delete mode 100644 src/__tests__/characterCodesToString.spec.ts delete mode 100644 src/__tests__/classic.spec.ts create mode 100644 src/__tests__/conformance/calendar.spec.ts create mode 100644 src/__tests__/conformance/characterCodesToAscii.spec.ts create mode 100644 src/__tests__/conformance/characterCodesToString.spec.ts create mode 100644 src/__tests__/conformance/classic.spec.ts create mode 100644 src/__tests__/conformance/hasSpecialCharacters.spec.ts create mode 100644 src/__tests__/conformance/parseComponent.spec.ts create mode 100644 src/__tests__/conformance/sanitizeSpecialCharacters.spec.ts create mode 100644 src/__tests__/conformance/support.ts create mode 100644 src/__tests__/conformance/vbml.spec.ts delete mode 100644 src/__tests__/hasSpecialCharacters.spec.ts delete mode 100644 src/__tests__/parseComponent.spec.ts delete mode 100644 src/__tests__/randomColors.spec.ts delete mode 100644 src/__tests__/sanitizeSpecialCharacters.spec.ts delete mode 100644 src/__tests__/vbml.spec.ts create mode 100644 test/README.md create mode 100644 test/expected/calendar/creates_calendar_with_four_weeks.json create mode 100644 test/expected/calendar/does_not_highlight_days_outside_of_month.json create mode 100644 test/expected/calendar/renders_single_date_header_when_last_week_has_one_day.json create mode 100644 test/expected/characterCodesToAscii/converts_color_codes.json create mode 100644 test/expected/characterCodesToAscii/handles_multiple_rows.json create mode 100644 test/expected/characterCodesToAscii/spaces_out_letters.json create mode 100644 test/expected/characterCodesToString/converts_two_line_sentence.json create mode 100644 test/expected/characterCodesToString/converts_word_to_string.json create mode 100644 test/expected/characterCodesToString/does_not_insert_line_break_when_first_word_fits.json create mode 100644 test/expected/characterCodesToString/handles_breaks.json create mode 100644 test/expected/characterCodesToString/handles_line_breaks.json create mode 100644 test/expected/classic/converts_ae_umlaut.json create mode 100644 test/expected/classic/converts_double_newline.json create mode 100644 test/expected/classic/converts_embedded_char_code_string.json create mode 100644 test/expected/classic/converts_emoji_colors.json create mode 100644 test/expected/classic/converts_hyphen_string.json create mode 100644 test/expected/classic/converts_long_string_with_digits.json create mode 100644 test/expected/classic/converts_longer_string.json create mode 100644 test/expected/classic/converts_single_char_code.json create mode 100644 test/expected/classic/converts_single_newline.json create mode 100644 test/expected/classic/converts_special_characters.json create mode 100644 test/expected/classic/converts_string_to_classic_board.json create mode 100644 test/expected/classic/preserves_triple_spaces.json create mode 100644 test/expected/classic/respects_double_spaces.json create mode 100644 test/expected/classic/returns_empty_board_for_empty_string.json create mode 100644 test/expected/hasSpecialCharacters/excludes_black_color_swatch.json create mode 100644 test/expected/hasSpecialCharacters/excludes_ios_double_quote.json create mode 100644 test/expected/hasSpecialCharacters/excludes_ios_single_quote.json create mode 100644 test/expected/hasSpecialCharacters/excludes_newlines.json create mode 100644 test/expected/hasSpecialCharacters/excludes_orange_color_swatch.json create mode 100644 test/expected/hasSpecialCharacters/excludes_white_color_swatch.json create mode 100644 test/expected/hasSpecialCharacters/includes_fractions.json create mode 100644 test/expected/hasSpecialCharacters/returns_false_for_empty_string.json create mode 100644 test/expected/hasSpecialCharacters/returns_false_for_lowercase_alphabet.json create mode 100644 test/expected/hasSpecialCharacters/returns_false_for_numbers.json create mode 100644 test/expected/hasSpecialCharacters/returns_false_for_supported_symbols.json create mode 100644 test/expected/hasSpecialCharacters/returns_false_for_uppercase_alphabet.json create mode 100644 test/expected/hasSpecialCharacters/returns_true_for_mixed_special_and_standard_characters.json create mode 100644 test/expected/hasSpecialCharacters/returns_true_for_special_character.json create mode 100644 test/expected/parseComponent/adds_extra_spaces.json create mode 100644 test/expected/parseComponent/adds_template_props.json create mode 100644 test/expected/parseComponent/allows_array_iteration.json create mode 100644 test/expected/parseComponent/allows_conditions_when_false.json create mode 100644 test/expected/parseComponent/allows_conditions_when_true.json create mode 100644 test/expected/parseComponent/allows_newlines.json create mode 100644 test/expected/parseComponent/allows_newlines_after_spaces.json create mode 100644 test/expected/parseComponent/allows_newlines_before_spaces.json create mode 100644 test/expected/parseComponent/automatically_breaks_line.json create mode 100644 test/expected/parseComponent/breaks_on_lines_with_character_codes.json create mode 100644 test/expected/parseComponent/converts_emoji_characters_to_character_codes.json create mode 100644 test/expected/parseComponent/does_not_break_when_unnecessary.json create mode 100644 test/expected/parseComponent/formats_longer_message_centered.json create mode 100644 test/expected/parseComponent/formats_longer_plain_text.json create mode 100644 test/expected/parseComponent/formats_plain_text.json create mode 100644 test/expected/parseComponent/horizontally_aligns_center.json create mode 100644 test/expected/parseComponent/horizontally_aligns_justified.json create mode 100644 test/expected/parseComponent/horizontally_aligns_right.json create mode 100644 test/expected/parseComponent/horizontally_and_vertically_aligned_center.json create mode 100644 test/expected/parseComponent/justified_long_complex_message.json create mode 100644 test/expected/parseComponent/justified_when_flowing_to_next_line.json create mode 100644 test/expected/parseComponent/justified_when_full_line_covered.json create mode 100644 test/expected/parseComponent/parses_absolute_component.json create mode 100644 test/expected/parseComponent/parses_character_codes.json create mode 100644 test/expected/parseComponent/parses_raw_component.json create mode 100644 test/expected/parseComponent/parses_two_digit_character_codes.json create mode 100644 test/expected/parseComponent/splits_long_words.json create mode 100644 test/expected/parseComponent/throws_for_invalid_character_codes.json create mode 100644 test/expected/parseComponent/vertically_aligns_bottom.json create mode 100644 test/expected/parseComponent/vertically_aligns_center.json create mode 100644 test/expected/parseComponent/vertically_aligns_center_multiple_rows.json create mode 100644 test/expected/parseComponent/vertically_aligns_center_sticks_to_top_when_no_even_padding.json create mode 100644 test/expected/sanitizeSpecialCharacters/accepts_whitespace_after_heart.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_cedilla.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_ellipsis.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_en_dash.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_f_hook.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_fraction_slash.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_german_lowercase_ae.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_german_lowercase_oe.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_german_lowercase_ue.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_german_uppercase_ae.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_german_uppercase_oe.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_german_uppercase_ue.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_ligature_ae.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_ligature_oe.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_micro_sign_to_space.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_scandinavian_a_ring.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_scandinavian_o_stroke.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_sharp_s.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_fussball.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_gross.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_weiss.json create mode 100644 test/expected/sanitizeSpecialCharacters/converts_unsupported_sequenced_emojis_to_whitespace.json create mode 100644 test/expected/sanitizeSpecialCharacters/does_not_modify_plain_text.json create mode 100644 test/expected/sanitizeSpecialCharacters/does_not_replace_vestaboard_heart.json create mode 100644 test/expected/sanitizeSpecialCharacters/handles_german_sharp_s_in_context.json create mode 100644 test/expected/sanitizeSpecialCharacters/handles_german_text_with_umlauts_in_context.json create mode 100644 test/expected/sanitizeSpecialCharacters/handles_heart_emoji_and_unsupported_emojis.json create mode 100644 test/expected/sanitizeSpecialCharacters/handles_mixed_special_characters.json create mode 100644 test/expected/sanitizeSpecialCharacters/handles_multiple_special_characters_together.json create mode 100644 test/expected/sanitizeSpecialCharacters/handles_sentence.json create mode 100644 test/expected/sanitizeSpecialCharacters/preserves_space_between_heart_and_emoji.json create mode 100644 test/expected/sanitizeSpecialCharacters/preserves_space_between_heart_and_latin_glyph.json create mode 100644 test/expected/sanitizeSpecialCharacters/preserves_spaces_between_hearts.json create mode 100644 test/expected/sanitizeSpecialCharacters/replaces_accented_character.json create mode 100644 test/expected/sanitizeSpecialCharacters/replaces_fractions_with_multiple_characters.json create mode 100644 test/expected/sanitizeSpecialCharacters/sanitizes_variation_selector_from_heart_emoji.json create mode 100644 test/expected/sanitizeSpecialCharacters/sanitizes_variation_selector_from_unicode_literal.json create mode 100644 test/expected/vbml/flows_third_component_to_next_line.json create mode 100644 test/expected/vbml/formats_ae_umlaut.json create mode 100644 test/expected/vbml/justifies_content_vertically.json create mode 100644 test/expected/vbml/justifies_content_vertically_three_chars.json create mode 100644 test/expected/vbml/layouts_absolute_components_by_relative_components.json create mode 100644 test/expected/vbml/layouts_absolute_components_with_raw_components_for_mountain_clock.json create mode 100644 test/expected/vbml/layouts_absolute_over_relative_components.json create mode 100644 test/expected/vbml/layouts_absolute_over_relative_components_standard_size.json create mode 100644 test/expected/vbml/layouts_calendar_component_for_christmas.json create mode 100644 test/expected/vbml/layouts_calendar_component_on_the_right.json create mode 100644 test/expected/vbml/layouts_calendar_component_with_other_components.json create mode 100644 test/expected/vbml/layouts_components_side_by_side.json create mode 100644 test/expected/vbml/layouts_components_vertically.json create mode 100644 test/expected/vbml/layouts_minimalist_calendar_component.json create mode 100644 test/expected/vbml/layouts_raw_components.json create mode 100644 test/expected/vbml/parses_single_component_on_a_board.json create mode 100644 test/expected/vbml/respects_double_returns.json create mode 100644 test/expected/vbml/respects_triple_returns.json create mode 100644 test/input/calendar/creates_calendar_with_four_weeks.json create mode 100644 test/input/calendar/does_not_highlight_days_outside_of_month.json create mode 100644 test/input/calendar/renders_single_date_header_when_last_week_has_one_day.json create mode 100644 test/input/characterCodesToAscii/converts_color_codes.json create mode 100644 test/input/characterCodesToAscii/handles_multiple_rows.json create mode 100644 test/input/characterCodesToAscii/spaces_out_letters.json create mode 100644 test/input/characterCodesToString/converts_two_line_sentence.json create mode 100644 test/input/characterCodesToString/converts_word_to_string.json create mode 100644 test/input/characterCodesToString/does_not_insert_line_break_when_first_word_fits.json create mode 100644 test/input/characterCodesToString/handles_breaks.json create mode 100644 test/input/characterCodesToString/handles_line_breaks.json create mode 100644 test/input/classic/converts_ae_umlaut.json create mode 100644 test/input/classic/converts_double_newline.json create mode 100644 test/input/classic/converts_embedded_char_code_string.json create mode 100644 test/input/classic/converts_emoji_colors.json create mode 100644 test/input/classic/converts_hyphen_string.json create mode 100644 test/input/classic/converts_long_string_with_digits.json create mode 100644 test/input/classic/converts_longer_string.json create mode 100644 test/input/classic/converts_single_char_code.json create mode 100644 test/input/classic/converts_single_newline.json create mode 100644 test/input/classic/converts_special_characters.json create mode 100644 test/input/classic/converts_string_to_classic_board.json create mode 100644 test/input/classic/preserves_triple_spaces.json create mode 100644 test/input/classic/respects_double_spaces.json create mode 100644 test/input/classic/returns_empty_board_for_empty_string.json create mode 100644 test/input/hasSpecialCharacters/excludes_black_color_swatch.json create mode 100644 test/input/hasSpecialCharacters/excludes_ios_double_quote.json create mode 100644 test/input/hasSpecialCharacters/excludes_ios_single_quote.json create mode 100644 test/input/hasSpecialCharacters/excludes_newlines.json create mode 100644 test/input/hasSpecialCharacters/excludes_orange_color_swatch.json create mode 100644 test/input/hasSpecialCharacters/excludes_white_color_swatch.json create mode 100644 test/input/hasSpecialCharacters/includes_fractions.json create mode 100644 test/input/hasSpecialCharacters/returns_false_for_empty_string.json create mode 100644 test/input/hasSpecialCharacters/returns_false_for_lowercase_alphabet.json create mode 100644 test/input/hasSpecialCharacters/returns_false_for_numbers.json create mode 100644 test/input/hasSpecialCharacters/returns_false_for_supported_symbols.json create mode 100644 test/input/hasSpecialCharacters/returns_false_for_uppercase_alphabet.json create mode 100644 test/input/hasSpecialCharacters/returns_true_for_mixed_special_and_standard_characters.json create mode 100644 test/input/hasSpecialCharacters/returns_true_for_special_character.json create mode 100644 test/input/parseComponent/adds_extra_spaces.json create mode 100644 test/input/parseComponent/adds_template_props.json create mode 100644 test/input/parseComponent/allows_array_iteration.json create mode 100644 test/input/parseComponent/allows_conditions_when_false.json create mode 100644 test/input/parseComponent/allows_conditions_when_true.json create mode 100644 test/input/parseComponent/allows_newlines.json create mode 100644 test/input/parseComponent/allows_newlines_after_spaces.json create mode 100644 test/input/parseComponent/allows_newlines_before_spaces.json create mode 100644 test/input/parseComponent/automatically_breaks_line.json create mode 100644 test/input/parseComponent/breaks_on_lines_with_character_codes.json create mode 100644 test/input/parseComponent/converts_emoji_characters_to_character_codes.json create mode 100644 test/input/parseComponent/does_not_break_when_unnecessary.json create mode 100644 test/input/parseComponent/formats_longer_message_centered.json create mode 100644 test/input/parseComponent/formats_longer_plain_text.json create mode 100644 test/input/parseComponent/formats_plain_text.json create mode 100644 test/input/parseComponent/horizontally_aligns_center.json create mode 100644 test/input/parseComponent/horizontally_aligns_justified.json create mode 100644 test/input/parseComponent/horizontally_aligns_right.json create mode 100644 test/input/parseComponent/horizontally_and_vertically_aligned_center.json create mode 100644 test/input/parseComponent/justified_long_complex_message.json create mode 100644 test/input/parseComponent/justified_when_flowing_to_next_line.json create mode 100644 test/input/parseComponent/justified_when_full_line_covered.json create mode 100644 test/input/parseComponent/parses_absolute_component.json create mode 100644 test/input/parseComponent/parses_character_codes.json create mode 100644 test/input/parseComponent/parses_raw_component.json create mode 100644 test/input/parseComponent/parses_two_digit_character_codes.json create mode 100644 test/input/parseComponent/splits_long_words.json create mode 100644 test/input/parseComponent/throws_for_invalid_character_codes.json create mode 100644 test/input/parseComponent/vertically_aligns_bottom.json create mode 100644 test/input/parseComponent/vertically_aligns_center.json create mode 100644 test/input/parseComponent/vertically_aligns_center_multiple_rows.json create mode 100644 test/input/parseComponent/vertically_aligns_center_sticks_to_top_when_no_even_padding.json create mode 100644 test/input/sanitizeSpecialCharacters/accepts_whitespace_after_heart.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_cedilla.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_ellipsis.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_en_dash.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_f_hook.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_fraction_slash.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_german_lowercase_ae.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_german_lowercase_oe.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_german_lowercase_ue.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_german_uppercase_ae.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_german_uppercase_oe.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_german_uppercase_ue.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_ligature_ae.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_ligature_oe.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_micro_sign_to_space.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_scandinavian_a_ring.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_scandinavian_o_stroke.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_sharp_s.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_sharp_s_in_fussball.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_sharp_s_in_gross.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_sharp_s_in_weiss.json create mode 100644 test/input/sanitizeSpecialCharacters/converts_unsupported_sequenced_emojis_to_whitespace.json create mode 100644 test/input/sanitizeSpecialCharacters/does_not_modify_plain_text.json create mode 100644 test/input/sanitizeSpecialCharacters/does_not_replace_vestaboard_heart.json create mode 100644 test/input/sanitizeSpecialCharacters/handles_german_sharp_s_in_context.json create mode 100644 test/input/sanitizeSpecialCharacters/handles_german_text_with_umlauts_in_context.json create mode 100644 test/input/sanitizeSpecialCharacters/handles_heart_emoji_and_unsupported_emojis.json create mode 100644 test/input/sanitizeSpecialCharacters/handles_mixed_special_characters.json create mode 100644 test/input/sanitizeSpecialCharacters/handles_multiple_special_characters_together.json create mode 100644 test/input/sanitizeSpecialCharacters/handles_sentence.json create mode 100644 test/input/sanitizeSpecialCharacters/preserves_space_between_heart_and_emoji.json create mode 100644 test/input/sanitizeSpecialCharacters/preserves_space_between_heart_and_latin_glyph.json create mode 100644 test/input/sanitizeSpecialCharacters/preserves_spaces_between_hearts.json create mode 100644 test/input/sanitizeSpecialCharacters/replaces_accented_character.json create mode 100644 test/input/sanitizeSpecialCharacters/replaces_fractions_with_multiple_characters.json create mode 100644 test/input/sanitizeSpecialCharacters/sanitizes_variation_selector_from_heart_emoji.json create mode 100644 test/input/sanitizeSpecialCharacters/sanitizes_variation_selector_from_unicode_literal.json create mode 100644 test/input/vbml/flows_third_component_to_next_line.json create mode 100644 test/input/vbml/formats_ae_umlaut.json create mode 100644 test/input/vbml/justifies_content_vertically.json create mode 100644 test/input/vbml/justifies_content_vertically_three_chars.json create mode 100644 test/input/vbml/layouts_absolute_components_by_relative_components.json create mode 100644 test/input/vbml/layouts_absolute_components_with_raw_components_for_mountain_clock.json create mode 100644 test/input/vbml/layouts_absolute_over_relative_components.json create mode 100644 test/input/vbml/layouts_absolute_over_relative_components_standard_size.json create mode 100644 test/input/vbml/layouts_calendar_component_for_christmas.json create mode 100644 test/input/vbml/layouts_calendar_component_on_the_right.json create mode 100644 test/input/vbml/layouts_calendar_component_with_other_components.json create mode 100644 test/input/vbml/layouts_components_side_by_side.json create mode 100644 test/input/vbml/layouts_components_vertically.json create mode 100644 test/input/vbml/layouts_minimalist_calendar_component.json create mode 100644 test/input/vbml/layouts_raw_components.json create mode 100644 test/input/vbml/parses_single_component_on_a_board.json create mode 100644 test/input/vbml/respects_double_returns.json create mode 100644 test/input/vbml/respects_triple_returns.json create mode 100644 test/platform-exceptions/php/vbml/layouts_absolute_components_by_relative_components.json create mode 100644 test/platform-exceptions/php/vbml/layouts_absolute_over_relative_components.json create mode 100644 test/platform-exceptions/python/classic/converts_special_characters.json create mode 100644 test/platform-exceptions/python/hasSpecialCharacters/excludes_ios_single_quote.json create mode 100644 test/platform-exceptions/python/vbml/layouts_absolute_components_by_relative_components.json create mode 100644 test/platform-exceptions/python/vbml/layouts_absolute_over_relative_components.json create mode 100644 test/platform-exceptions/python/vbml/layouts_calendar_component_for_christmas.json create mode 100644 test/platform-exceptions/python/vbml/layouts_calendar_component_on_the_right.json create mode 100644 test/platform-exceptions/python/vbml/layouts_calendar_component_with_other_components.json create mode 100644 test/platform-exceptions/python/vbml/layouts_minimalist_calendar_component.json diff --git a/src/__tests__/calendar.spec.ts b/src/__tests__/calendar.spec.ts deleted file mode 100644 index 722363b..0000000 --- a/src/__tests__/calendar.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { makeCalendar } from "../calendar"; - -describe("Calendar", () => { - it("Should create calendar with only 4 weeks", () => { - const result = makeCalendar( - "2", - "2026", - {}, - ); - expect(result).toEqual([ - [28, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should render single date header when last week has one day", () => { - const result = makeCalendar( - "2", - "2027", - {}, - ); - expect(result).toEqual([ - [28, 59, 28, 33, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should not highlight days outside of month", () => { - const result = makeCalendar( - "6", - "2026", - { "30": 67, "31": 67, "32": 67 }, - ); - expect(result).toEqual([ - [32, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 44, 29, 36, 65, 65, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); -}); diff --git a/src/__tests__/characterCodesToAscii.spec.ts b/src/__tests__/characterCodesToAscii.spec.ts deleted file mode 100644 index e7ac235..0000000 --- a/src/__tests__/characterCodesToAscii.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { characterCodesToAscii } from "../characterCodesToAscii"; - -describe("Character codes to ASCII", () => { - it("Should convert colors", () => { - const result = characterCodesToAscii([[63, 64, 65, 66, 67, 68, 69, 70]]); - expect(result).toEqual("šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›"); - }); - it("Should handle rows", () => { - const result = characterCodesToAscii([ - [63, 64], - [63, 64], - ]); - expect(result).toEqual(`🟄🟧\n\n🟄🟧`); - }); - it("Should space out letters", () => { - const result = characterCodesToAscii([[1, 2]]); - expect(result).toEqual(`A B `); - }); -}); diff --git a/src/__tests__/characterCodesToString.spec.ts b/src/__tests__/characterCodesToString.spec.ts deleted file mode 100644 index 16e18d0..0000000 --- a/src/__tests__/characterCodesToString.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { characterCodesToString } from "../characterCodesToString"; - -describe("Convert array of array of characters to a string", () => { - it("Should covert a word to a string", () => { - const result = characterCodesToString([[1, 2]]); - expect(result).toEqual("AB"); - }); - - it("Should convert two-line sentence", () => { - const result = characterCodesToString([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 20, 8, 9, 19, 0, 9, 19, 0, 1, 0, 12, 15, 14, 7, 5, 18, 0, 2, 12, 15, 3, - 11, - ], - [ - 20, 8, 1, 20, 0, 19, 16, 1, 14, 19, 0, 28, 0, 12, 9, 14, 5, 19, 0, 0, 0, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - - expect(result).toEqual("THIS IS A LONGER BLOCK THAT SPANS 2 LINES"); - }); - - it("Should handle breaks", () => { - const result = characterCodesToString([ - [0, 0, 8, 1, 14, 4, 12, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 0, 0, 2, 18, 5, 1, 11, 19, 0, 7, 18, 1, 3, 5, 6, 21, 12, 12, 25, 0, 0, - 0, - ], - ]); - - expect(result).toEqual("HANDLE BREAKS GRACEFULLY"); - }); - - it("Should handle line breaks", () => { - const result = characterCodesToString( - [ - [1, 2, 0, 0, 0], - [3, 4, 0, 0, 0], - ], - { - allowLineBreaks: true, - } - ); - - expect(result).toEqual("AB\nCD"); - }); - - it("Should assume there is no line break if the first word can fit on the previous line", () => { - const result = characterCodesToString( - [ - [1, 0], - [2, 0], - ], - { - allowLineBreaks: true, - } - ); - - expect(result).toEqual("A B"); - }); -}); diff --git a/src/__tests__/classic.spec.ts b/src/__tests__/classic.spec.ts deleted file mode 100644 index 22131dd..0000000 --- a/src/__tests__/classic.spec.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { classic } from "../classic"; - -describe("Classic", () => { - it("Should convert string to classic board", () => { - const mockBoard = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 0, 0, 0, 0, 8, 5, 12, 12, 15, 55, 0, 23, 15, 18, 12, 4, 37, 0, 0, 0, 0, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]; - const string = "Hello, World!"; - const classicBoard = classic(string); - expect(classicBoard).toEqual(mockBoard); - }); - - it("Should convert embedded char code string to classic board", () => { - const mockBoard = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 0, 8, 5, 12, 12, 15, 55, 0, 23, 15, 18, 12, 4, 37, 0, 29, 33, 0, 0, 0, - 0, 0, - ], - [ - 0, 65, 32, 32, 65, 0, 34, 35, 65, 0, 65, 28, 36, 0, 8, 5, 12, 12, 15, 0, - 0, 0, - ], - [ - 0, 23, 15, 18, 12, 4, 0, 23, 8, 1, 20, 19, 21, 16, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]; - const string = - "Hello, World{37} 37 {65}66{65} 89{65} {65}20 hello world whatsup"; - const classicBoard = classic(string, { - extraHPadding: 0, - }); - expect(classicBoard).toEqual(mockBoard); - }); - - it("Should convert longer string to classic board", () => { - const string = "reallylongwordthatismorethantwentytwocharcters"; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 0, 18, 5, 1, 12, 12, 25, 12, 15, 14, 7, 23, 15, 18, 4, 20, 8, 1, 20, 0, - 0, 0, - ], - [ - 0, 9, 19, 13, 15, 18, 5, 20, 8, 1, 14, 20, 23, 5, 14, 20, 25, 20, 23, 0, - 0, 0, - ], - [0, 15, 3, 8, 1, 18, 3, 20, 5, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - it("Should convert Ƥ to ae", () => { - const string = "ƤƄ"; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should convert long string with digits to classic board", () => { - const string = "reallylongwordthatismorethan22charcters"; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 18, 5, 1, 12, 12, 25, 12, 15, 14, 7, 23, 15, 18, 4, 20, 8, 1, 20, 9, 19, - 13, 15, - ], - [ - 18, 5, 20, 8, 1, 14, 28, 28, 3, 8, 1, 18, 3, 20, 5, 18, 19, 0, 0, 0, 0, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should convert single new line string to classic board", () => { - const string = `hello - world`; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should convert double newline string to classic board", () => { - const string = `hello\n\nworld`; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - it("Should convert string to classic board", () => { - const string = ``; - const classicBoard = classic(string); - const emptyRow = new Array(22).fill(0); - - expect(classicBoard).toEqual([ - emptyRow, - emptyRow, - emptyRow, - emptyRow, - emptyRow, - emptyRow, - ]); - }); - - it("Should convert char code 1 string to classic board with `a`", () => { - const string = `{1}`; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - it("Should convert hyphen string to classic board", () => { - const string = `- -hyphen`; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 44, 0, 44, 8, 25, 16, 8, 5, 14, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should convert special character strings to classic board", () => { - const string = `!@#$%^&*()_+Ć„ĆŸāˆ‚Ę’Ā©Ė™āˆ†ĖšĀ¬ĀµāˆšĆ§āˆ«ĖœĀµā‰¤ā‰„Ć·{}`; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 37, 38, 39, 40, 54, 0, 47, 0, 41, 42, 46, 1, 19, 19, 0, 6, 0, 0, 0, 0, - 0, 0, - ], - [0, 3, 0, 0, 0, 0, 0, 0, 41, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should convert emoji colors to classic board", () => { - const string = "šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›"; - const classicBoard = classic(string); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 0, 0, 0, 0, 0, 0, 63, 64, 65, 66, 67, 68, 69, 70, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should respect double spaces in string", () => { - const string = "hello world"; - const classicBoard = classic(string, { - preserveDoubleSpaces: true, - }); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should preserve triple spaces", () => { - const string = "hello world"; - const classicBoard = classic(string, { - preserveDoubleSpaces: true, - }); - expect(classicBoard).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); -}); diff --git a/src/__tests__/conformance/calendar.spec.ts b/src/__tests__/conformance/calendar.spec.ts new file mode 100644 index 0000000..bd0f6fa --- /dev/null +++ b/src/__tests__/conformance/calendar.spec.ts @@ -0,0 +1,35 @@ +import { makeCalendar, VBMLDays } from "../../calendar"; +import { runConformanceSuite } from "./support"; + +interface CalendarInput { + month: string; + year: string; + days?: VBMLDays; + defaultDayColor?: 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71; + hideSMTWTFS?: boolean; + hideDates?: boolean; + hideMonthYear?: boolean; +} + +runConformanceSuite({ + suiteName: "Calendar conformance", + suiteDir: "calendar", + run: ({ + month, + year, + days = {}, + defaultDayColor, + hideSMTWTFS, + hideDates, + hideMonthYear, + }) => + makeCalendar( + month, + year, + days, + defaultDayColor, + hideSMTWTFS, + hideDates, + hideMonthYear + ), +}); diff --git a/src/__tests__/conformance/characterCodesToAscii.spec.ts b/src/__tests__/conformance/characterCodesToAscii.spec.ts new file mode 100644 index 0000000..c6a2a8b --- /dev/null +++ b/src/__tests__/conformance/characterCodesToAscii.spec.ts @@ -0,0 +1,14 @@ +import { characterCodesToAscii } from "../../characterCodesToAscii"; +import { runConformanceSuite } from "./support"; + +interface CharacterCodesToAsciiInput { + characterCodes: number[][]; + isWhite?: boolean; +} + +runConformanceSuite({ + suiteName: "Character codes to ASCII conformance", + suiteDir: "characterCodesToAscii", + run: ({ characterCodes, isWhite }) => + characterCodesToAscii(characterCodes, isWhite), +}); diff --git a/src/__tests__/conformance/characterCodesToString.spec.ts b/src/__tests__/conformance/characterCodesToString.spec.ts new file mode 100644 index 0000000..afa93b5 --- /dev/null +++ b/src/__tests__/conformance/characterCodesToString.spec.ts @@ -0,0 +1,15 @@ +import { characterCodesToString } from "../../characterCodesToString"; +import { runConformanceSuite } from "./support"; + +interface CharacterCodesToStringInput { + characters: number[][]; + options?: { + allowLineBreaks?: boolean; + }; +} + +runConformanceSuite({ + suiteName: "Character codes to string conformance", + suiteDir: "characterCodesToString", + run: ({ characters, options }) => characterCodesToString(characters, options), +}); diff --git a/src/__tests__/conformance/classic.spec.ts b/src/__tests__/conformance/classic.spec.ts new file mode 100644 index 0000000..369a44b --- /dev/null +++ b/src/__tests__/conformance/classic.spec.ts @@ -0,0 +1,16 @@ +import { classic } from "../../classic"; +import { runConformanceSuite } from "./support"; + +interface ClassicInput { + text: string; + options?: { + extraHPadding?: number; + preserveDoubleSpaces?: boolean; + }; +} + +runConformanceSuite({ + suiteName: "Classic conformance", + suiteDir: "classic", + run: ({ text, options }) => classic(text, options), +}); diff --git a/src/__tests__/conformance/hasSpecialCharacters.spec.ts b/src/__tests__/conformance/hasSpecialCharacters.spec.ts new file mode 100644 index 0000000..c92a6df --- /dev/null +++ b/src/__tests__/conformance/hasSpecialCharacters.spec.ts @@ -0,0 +1,12 @@ +import { hasSpecialCharacters } from "../../hasSpecialCharacters"; +import { runConformanceSuite } from "./support"; + +interface HasSpecialCharactersInput { + text: string; +} + +runConformanceSuite({ + suiteName: "Has special characters conformance", + suiteDir: "hasSpecialCharacters", + run: ({ text }) => hasSpecialCharacters(text), +}); diff --git a/src/__tests__/conformance/parseComponent.spec.ts b/src/__tests__/conformance/parseComponent.spec.ts new file mode 100644 index 0000000..a01fc96 --- /dev/null +++ b/src/__tests__/conformance/parseComponent.spec.ts @@ -0,0 +1,34 @@ +import { + parseAbsoluteComponent, + parseComponent, +} from "../../parseComponent"; +import { IVBMLComponent, VBMLProps } from "../../types"; +import { runConformanceSuite } from "./support"; + +interface ParseComponentInput { + mode?: "component" | "absolute"; + height: number; + width: number; + props?: VBMLProps; + component: IVBMLComponent; +} + +type ParseComponentOutput = + | number[][] + | { + characters: number[][]; + x: number; + y: number; + }; + +runConformanceSuite({ + suiteName: "Parse component conformance", + suiteDir: "parseComponent", + run: ({ mode = "component", height, width, props, component }) => { + if (mode === "absolute") { + return parseAbsoluteComponent(height, width, props)(component); + } + + return parseComponent(height, width, props)(component); + }, +}); diff --git a/src/__tests__/conformance/sanitizeSpecialCharacters.spec.ts b/src/__tests__/conformance/sanitizeSpecialCharacters.spec.ts new file mode 100644 index 0000000..f79e0b9 --- /dev/null +++ b/src/__tests__/conformance/sanitizeSpecialCharacters.spec.ts @@ -0,0 +1,12 @@ +import { sanitizeSpecialCharacters } from "../../sanitizeSpecialCharacters"; +import { runConformanceSuite } from "./support"; + +interface SanitizeSpecialCharactersInput { + text: string; +} + +runConformanceSuite({ + suiteName: "Sanitize special characters conformance", + suiteDir: "sanitizeSpecialCharacters", + run: ({ text }) => sanitizeSpecialCharacters(text), +}); diff --git a/src/__tests__/conformance/support.ts b/src/__tests__/conformance/support.ts new file mode 100644 index 0000000..5ca65d2 --- /dev/null +++ b/src/__tests__/conformance/support.ts @@ -0,0 +1,180 @@ +import fs from "fs"; +import path from "path"; + +interface ConformanceErrorExpectation { + message: string; +} + +interface PlatformException { + reason: string; + result?: TExpected; + error?: ConformanceErrorExpectation; + skip?: boolean; +} + +interface ConformanceExpected { + result?: TExpected; + error?: ConformanceErrorExpectation; +} + +interface ConformanceCase { + id: string; + input: TInput; + expected: ConformanceExpected; +} + +interface ResolvedExpectation { + result?: TExpected; + error?: ConformanceErrorExpectation; + skip?: string; +} + +interface RunConformanceSuiteOptions { + suiteName: string; + suiteDir: string; + run: (input: TInput) => TExpected; + platform?: string; +} + +const readJsonFile = (filePath: string): T => + JSON.parse(fs.readFileSync(filePath, "utf8")) as T; + +const walkJsonFiles = (rootDir: string): string[] => { + const entries = fs.readdirSync(rootDir, { + withFileTypes: true, + }); + + return entries + .flatMap((entry) => { + const fullPath = path.join(rootDir, entry.name); + + if (entry.isDirectory()) { + return walkJsonFiles(fullPath); + } + + return entry.name.endsWith(".json") ? [fullPath] : []; + }) + .sort(); +}; + +const loadCases = (suiteDir: string): Array> => { + const inputRoot = path.resolve(__dirname, "../../../test/input", suiteDir); + const expectedRoot = path.resolve(__dirname, "../../../test/expected", suiteDir); + + return walkJsonFiles(inputRoot).map((inputPath) => { + const relativePath = path.relative(inputRoot, inputPath); + const expectedPath = path.join(expectedRoot, relativePath); + + if (!fs.existsSync(expectedPath)) { + throw new Error( + `Missing expected file for ${suiteDir}/${relativePath}.` + ); + } + + return { + id: relativePath.replace(/\.json$/, ""), + input: readJsonFile(inputPath), + expected: readJsonFile>(expectedPath), + }; + }); +}; + +const loadPlatformException = ( + platform: string, + suiteDir: string, + testId: string +): PlatformException | null => { + const exceptionPath = path.resolve( + __dirname, + "../../../test/platform-exceptions", + platform, + suiteDir, + `${testId}.json` + ); + + if (!fs.existsSync(exceptionPath)) { + return null; + } + + return readJsonFile>(exceptionPath); +}; + +const resolveExpectation = ( + platform: string, + suiteDir: string, + testCase: ConformanceCase +): ResolvedExpectation => { + const exception = loadPlatformException( + platform, + suiteDir, + testCase.id + ); + + if (exception) { + if (!exception.reason.trim()) { + throw new Error( + `Platform exception "${suiteDir}/${testCase.id}" is missing a reason for ${platform}.` + ); + } + + if (exception.skip) { + if (exception.result !== undefined || exception.error !== undefined) { + throw new Error( + `Platform exception "${suiteDir}/${testCase.id}" cannot define skip with result or error.` + ); + } + + return { + skip: exception.reason, + }; + } + + if ((exception.result === undefined) === (exception.error === undefined)) { + throw new Error( + `Platform exception "${suiteDir}/${testCase.id}" must define exactly one of result or error.` + ); + } + + return { + result: exception.result, + error: exception.error, + }; + } + + if ((testCase.expected.result === undefined) === (testCase.expected.error === undefined)) { + throw new Error( + `Conformance case "${suiteDir}/${testCase.id}" must define exactly one of result or error.` + ); + } + + return { + result: testCase.expected.result, + error: testCase.expected.error, + }; +}; + +export const runConformanceSuite = ({ + suiteName, + suiteDir, + run, + platform = "node", +}: RunConformanceSuiteOptions): void => { + const cases = loadCases(suiteDir); + + describe(suiteName, () => { + cases.forEach((testCase) => { + const resolved = resolveExpectation(platform, suiteDir, testCase); + const testMethod = resolved.skip ? it.skip : it; + const label = testCase.id; + + testMethod(label, () => { + if (resolved.error) { + expect(() => run(testCase.input)).toThrow(resolved.error.message); + return; + } + + expect(run(testCase.input)).toEqual(resolved.result); + }); + }); + }); +}; diff --git a/src/__tests__/conformance/vbml.spec.ts b/src/__tests__/conformance/vbml.spec.ts new file mode 100644 index 0000000..652b2ae --- /dev/null +++ b/src/__tests__/conformance/vbml.spec.ts @@ -0,0 +1,9 @@ +import { vbml } from "../.."; +import { IVBML } from "../../types"; +import { runConformanceSuite } from "./support"; + +runConformanceSuite({ + suiteName: "VBML conformance", + suiteDir: "vbml", + run: (input) => vbml.parse(input), +}); diff --git a/src/__tests__/hasSpecialCharacters.spec.ts b/src/__tests__/hasSpecialCharacters.spec.ts deleted file mode 100644 index fe22af6..0000000 --- a/src/__tests__/hasSpecialCharacters.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { hasSpecialCharacters } from "../hasSpecialCharacters"; - -describe("hasSpecialCharacters", () => { - it("should return true if text contains special characters", () => { - const text = "Ƥ"; - expect(hasSpecialCharacters(text)).toBeTruthy(); - }); - - it("should return true if text contains special characters mixed with standard characters", () => { - const text = "Ƥa"; - expect(hasSpecialCharacters(text)).toBeTruthy(); - }); - - it("should return false if text does not contain special characters", () => { - const text = "abcdefghijklmnopqrstuvwxyz"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("should return false if text does not contain special characters (uppercased)", () => { - const text = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should return false if text is standard numbers", () => { - const text = "0123456789"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should return false if text is standard symbols supported by Vestaboard", () => { - const text = "!@#$()-+&=;:'\"%,./?°"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("should return false if text is empty", () => { - const text = ""; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should exclude newlines", () => { - const text = "Hello\nWorld"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should exclude the single quote from the iOS keyboard", () => { - const text = "ā€˜"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should exclude the double quote from the iOS keyboard", () => { - const text = "ā€œ"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should exclude white color swatch", () => { - const text = "⬜"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should exclude black color swatch", () => { - const text = "⬛"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should exclude orange color swatch", () => { - const text = "🟧"; - expect(hasSpecialCharacters(text)).toBeFalsy(); - }); - - it("Should include fractions", () => { - const text = "½"; - expect(hasSpecialCharacters(text)).toBeTruthy(); - }); -}); diff --git a/src/__tests__/parseComponent.spec.ts b/src/__tests__/parseComponent.spec.ts deleted file mode 100644 index 8caa178..0000000 --- a/src/__tests__/parseComponent.spec.ts +++ /dev/null @@ -1,481 +0,0 @@ -import { parseAbsoluteComponent, parseComponent } from "../parseComponent"; -import { Align, IVBMLComponent, Justify } from "../types"; - -describe("Parse Component", () => { - it("Should format a message with plain text", () => { - const input: IVBMLComponent = { - template: "Hello World!", - }; - const result = parseComponent(1, 12)(input); - expect(result).toEqual([[8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37]]); - }); - - it("Should format a longer message with plain text", () => { - const input: IVBMLComponent = { - template: "Thank you for having us!", - }; - - const result = parseComponent(2, 12)(input); - expect(result).toEqual([ - [20, 8, 1, 14, 11, 0, 25, 15, 21, 0, 0, 0], - [6, 15, 18, 0, 8, 1, 22, 9, 14, 7, 0, 0], - ]); - }); - - - it("Should format a longer message center with plain text", () => { - const input: IVBMLComponent = { - template: "Thank you for having us!", - style: { - justify: Justify.center, - }, - }; - - const result = parseComponent(2, 22)(input); - - expect(result).toEqual([ - [ - 0, 20, 8, 1, 14, 11, 0, 25, 15, 21, 0, 6, 15, 18, 0, 8, 1, 22, 9, 14, 7, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 19, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should add extra spaces", () => { - const input: IVBMLComponent = { - template: "Hello World!", - }; - - const result = parseComponent(1, 13)(input); - expect(result).toEqual([[8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37, 0]]); - }); - - it("Should automatically break the line", () => { - const input: IVBMLComponent = { - template: "Hello World!", - }; - - const result = parseComponent(2, 6)(input); - expect(result).toEqual([ - [8, 5, 12, 12, 15, 0], - [23, 15, 18, 12, 4, 37], - ]); - }); - - it("Should not break the line if it doesn't need to", () => { - const input: IVBMLComponent = { - template: "Hello World!", - }; - - const result = parseComponent(3, 13)(input); - expect(result).toEqual([ - [8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should vertically align bottom", () => { - const input: IVBMLComponent = { - template: "!", - style: { - align: Align.bottom, - }, - }; - - const result = parseComponent(4, 1)(input); - expect(result).toEqual([[0], [0], [0], [37]]); - }); - - it("Should vertically align to the center", () => { - const input: IVBMLComponent = { - template: "!", - style: { - align: Align.center, - }, - }; - - const result = parseComponent(3, 1)(input); - expect(result).toEqual([[0], [37], [0]]); - }); - - it("Should vertically align to the center with multiple rows", () => { - const input: IVBMLComponent = { - template: "!", - style: { - align: Align.center, - }, - }; - - const result = parseComponent(5, 1)(input); - expect(result).toEqual([[0], [0], [37], [0], [0]]); - }); - - it("Should vertically align to the center by sticking to the top if there is not even padding", () => { - const input: IVBMLComponent = { - template: "!", - style: { - align: Align.center, - }, - }; - - const result = parseComponent(6, 1)(input); - expect(result).toEqual([[0], [0], [37], [0], [0], [0]]); - }); - - it("Should horizontally align right", () => { - const input: IVBMLComponent = { - template: "!", - style: { - justify: Justify.right, - }, - }; - - const result = parseComponent(1, 3)(input); - expect(result).toEqual([[0, 0, 37]]); - }); - - it("Should horizontally align center", () => { - const input: IVBMLComponent = { - template: "!", - style: { - justify: Justify.center, - }, - }; - - const result = parseComponent(1, 3)(input); - expect(result).toEqual([[0, 37, 0]]); - }); - - it("Should horizontally align justified", () => { - const input: IVBMLComponent = { - template: "Testing Testing 123", - style: { - align: Align.center, - justify: Justify.justified, - }, - }; - - const result = parseComponent(6, 22)(input); - - expect(result).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 0, 20, 5, 19, 20, 9, 14, 7, 0, 20, 5, 19, 20, 9, 14, 7, 0, 27, 28, 29, - 0, 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should horizontally align justified when the full line is covered", () => { - const input: IVBMLComponent = { - template: "Testing Testing 123456", - style: { - align: Align.center, - justify: Justify.justified, - }, - }; - - const result = parseComponent(6, 22)(input); - - expect(result).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 20, 5, 19, 20, 9, 14, 7, 0, 20, 5, 19, 20, 9, 14, 7, 0, 27, 28, 29, 30, - 31, 32, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should horizontally align justified when we flow to the next line", () => { - const input: IVBMLComponent = { - template: "Testing Testing 123456789", - style: { - align: Align.center, - justify: Justify.justified, - }, - }; - - const result = parseComponent(6, 22)(input); - - expect(result).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 0, 0, 0, 20, 5, 19, 20, 9, 14, 7, 0, 20, 5, 19, 20, 9, 14, 7, 0, 0, 0, - 0, - ], - [ - 0, 0, 0, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - it("Should horizontally align justified a long complex message", () => { - const input: IVBMLComponent = { - template: - "Pack my box with five dozen liquor jugs. The quick brown fox jumps over the lazy dog. How vexingly quick daft zebras jump!", - style: { - align: Align.center, - justify: Justify.justified, - }, - }; - - const result = parseComponent(6, 22)(input); - - expect(result).toEqual([ - [ - 16, 1, 3, 11, 0, 13, 25, 0, 2, 15, 24, 0, 23, 9, 20, 8, 0, 6, 9, 22, 5, - 0, - ], - [ - 4, 15, 26, 5, 14, 0, 12, 9, 17, 21, 15, 18, 0, 10, 21, 7, 19, 56, 0, 20, - 8, 5, - ], - [ - 17, 21, 9, 3, 11, 0, 2, 18, 15, 23, 14, 0, 6, 15, 24, 0, 10, 21, 13, 16, - 19, 0, - ], - [ - 15, 22, 5, 18, 0, 20, 8, 5, 0, 12, 1, 26, 25, 0, 4, 15, 7, 56, 0, 8, 15, - 23, - ], - [ - 22, 5, 24, 9, 14, 7, 12, 25, 0, 17, 21, 9, 3, 11, 0, 4, 1, 6, 20, 0, 0, - 0, - ], - [ - 26, 5, 2, 18, 1, 19, 0, 10, 21, 13, 16, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - ]); - }); - - it("Should horizontally and vertically align center", () => { - const input: IVBMLComponent = { - template: "!", - style: { - justify: Justify.center, - align: Align.center, - }, - }; - - const result = parseComponent(3, 3)(input); - expect(result).toEqual([ - [0, 0, 0], - [0, 37, 0], - [0, 0, 0], - ]); - }); - - it("Should parse character codes", () => { - const input: IVBMLComponent = { - template: "{1}{2}{3}", - style: { - justify: Justify.center, - align: Align.center, - }, - }; - - const result = parseComponent(1, 3)(input); - expect(result).toEqual([[1, 2, 3]]); - }); - - it("Should break on lines with character codes", () => { - const input: IVBMLComponent = { - template: "{1}{2} {3}{4}", - style: { - justify: Justify.center, - align: Align.center, - }, - }; - - const result = parseComponent(2, 3)(input); - expect(result).toEqual([ - [1, 2, 0], - [3, 4, 0], - ]); - }); - - it("Should parse two-digit character codes", () => { - const input: IVBMLComponent = { - template: "{68}{69}", - style: { - justify: Justify.center, - align: Align.center, - }, - }; - - const result = parseComponent(1, 2)(input); - - expect(result).toEqual([[68, 69]]); - }); - - it("Should throw for invalid character codes", () => { - const input: IVBMLComponent = { - template: "{99}", - style: { - justify: Justify.center, - align: Align.center, - }, - }; - - expect(() => { - parseComponent(1, 1)(input); - }).toThrow(); - }); - - it("Should allow newlines", () => { - const input: IVBMLComponent = { - template: "{1}\n{1}", - }; - - const result = parseComponent(2, 2)(input); - - expect(result).toEqual([ - [1, 0], - [1, 0], - ]); - }); - - it("Should allow newlines after spaces", () => { - const input: IVBMLComponent = { - template: "{1} \n{1}", - }; - - const result = parseComponent(2, 2)(input); - - expect(result).toEqual([ - [1, 0], - [1, 0], - ]); - }); - - it("Should allow newlines before spaces", () => { - const input: IVBMLComponent = { - template: "{1}\n{70}{1}", - style: { - justify: Justify.center, - }, - }; - - const result = parseComponent(2, 2)(input); - - expect(result).toEqual([ - [1, 0], - [70, 1], - ]); - }); - - it("Should add template props", () => { - const input: IVBMLComponent = { - template: "{{greeting}} World", - }; - - const result = parseComponent(1, 11, { - greeting: "Hello", - })(input); - - expect(result).toEqual([[8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4]]); - }); - - it("Should allow conditions", () => { - const input: IVBMLComponent = { - template: "I am {{#isHappy}}Happy{{/isHappy}}{{^isHappy}}Mad{{/isHappy}}", - }; - - const result = parseComponent(1, 10, { - isHappy: true, - })(input); - - expect(result).toEqual([[9, 0, 1, 13, 0, 8, 1, 16, 16, 25]]); - - const result2 = parseComponent(1, 10, { - isHappy: false, - })(input); - - expect(result2).toEqual([[9, 0, 1, 13, 0, 13, 1, 4, 0, 0]]); - }); - - it("Should allow arrays to be iterated", () => { - const input: IVBMLComponent = { - template: "{{#numbers}}{{.}}{{/numbers}}", - }; - - const result = parseComponent(1, 3, { - numbers: [1, 2, 3], - })(input); - - expect(result).toEqual([[27, 28, 29]]); - }); - - it("Should split long words", () => { - const input: IVBMLComponent = { - template: "{1}{2}{3}{4}", - }; - - const result = parseComponent(2, 2)(input); - - expect(result).toEqual([ - [1, 2], - [3, 4], - ]); - }); - it("Should parse absolute component", () => { - const input: IVBMLComponent = { - template: "Hello World!", - style: { - absolutePosition: { - x: 4, - y: 2, - }, - width: 6, - height: 2, - }, - }; - - const result = parseAbsoluteComponent(3, 12)(input); - expect(result).toEqual({ - characters: [ - [8, 5, 12, 12, 15, 0], - [23, 15, 18, 12, 4, 37], - ], - x: 4, - y: 2, - }); - }); - - it("Should parse a raw component", () => { - const input: IVBMLComponent = { - rawCharacters: [ - [1, 2], - [3, 4], - ], - }; - const result = parseComponent(3, 12)(input); - expect(result).toEqual([ - [1, 2], - [3, 4], - ]); - }); - - it("Should convert emoji characters to character codes", () => { - const input: IVBMLComponent = { - template: "šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›", - }; - const result = parseComponent(1, 8)(input); - expect(result).toEqual([[63, 64, 65, 66, 67, 68, 69, 70]]); - }); -}); diff --git a/src/__tests__/randomColors.spec.ts b/src/__tests__/randomColors.spec.ts deleted file mode 100644 index fae5585..0000000 --- a/src/__tests__/randomColors.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { colorCodes, randomColors } from "../randomColors"; - -describe("Random colors", () => { - it("Should fill a board with random colors", () => { - // Take 3 random colors from the default colorCodes - const colors = colorCodes.sort(() => 0.5 - Math.random()).slice(0, 3); - const result = randomColors(6, 22, colors); - - expect(result.length).toBe(6); - expect(result[0].length).toBe(22); - }); -}); diff --git a/src/__tests__/sanitizeSpecialCharacters.spec.ts b/src/__tests__/sanitizeSpecialCharacters.spec.ts deleted file mode 100644 index ff2c119..0000000 --- a/src/__tests__/sanitizeSpecialCharacters.spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { sanitizeSpecialCharacters } from "../sanitizeSpecialCharacters"; - -describe("Sanitize special characters", () => { - it("Should not modify text without special characters", () => { - const text = "abcdefghijklmnopqrstuvwxyz"; - expect(sanitizeSpecialCharacters(text)).toEqual(text); - }); - - it("Should replace special characters with their equivalent", () => { - const text = "ƃ"; - expect(sanitizeSpecialCharacters(text)).toEqual("a"); - }); - - it("Should handle a sentence or two", () => { - const text = "hello world"; - expect(sanitizeSpecialCharacters(text)).toEqual("hello world"); - }); - - it("Should handle mixed special characters in text", () => { - const text = "hĆ©llo wĆ“rld"; - expect(sanitizeSpecialCharacters(text)).toEqual("hello world"); - }); - - it("Should handle multiple special characters together", () => { - const text = "ëï"; - expect(sanitizeSpecialCharacters(text)).toEqual("ei"); - }); - - it("Should replace fractions with multiple characters", () => { - const text = "½"; - expect(sanitizeSpecialCharacters(text)).toEqual("1/2"); - }); - - it("Should sanitize variation selector-16 (U+FE0F) from ā¤ļø", () => { - const text = "ā¤ļø"; - expect(sanitizeSpecialCharacters(text)).toEqual("ā¤"); - }); - - it("Should sanitize variation selector-16 (U+FE0F) from the string literal \\u2764\\uFE0F", () => { - const text = "\u2764\uFE0F"; - expect(sanitizeSpecialCharacters(text)).toEqual("\u2764"); - }); - - it("Should not replace Vestaboard Note unicode hearts (U+2764)", () => { - const text = "\u2764"; - expect(sanitizeSpecialCharacters(text)).toEqual("ā¤"); - }); - - it("Should accept whitespace after \u2764 (U+2764)", () => { - const text = "\u2764 "; - expect(sanitizeSpecialCharacters(text)).toEqual(text); - }); - - it("Should not clear whitespace between black heart unicode characters", () => { - const testString = "ā¤ ā¤ ā¤ ā¤ ā¤"; - const result = sanitizeSpecialCharacters(testString); - expect(result).toEqual(testString); - }); - - it("Should not trim whitespace when \u2764 if followed by a latin glyph", () => { - const testString = "\u2764 A"; - const result = sanitizeSpecialCharacters(testString); - expect(result).toEqual(testString); - }); - - it("Should not trim whitespace when \u2764 is followed by an emoji", () => { - const testString = "\u2764 🟧"; - const result = sanitizeSpecialCharacters(testString); - expect(result).toEqual(testString); - }); - - it("Should convert unsupported, sequenced emojis to whitespace", () => { - const testString = "ā˜ ļøāš ļøāœ…ā–¶ļøāœØāŒ›ļø"; - const equivalentWhitespace = "\u0020\u0020\u0020\u0020\u0020\u0020"; - const result = sanitizeSpecialCharacters(testString); - expect(result).toEqual(equivalentWhitespace); - }); - - it("Should handle the heart emoji and unsupported emojis", () => { - const testString = "ā¤ļøā˜ ļøāš ļøāœ…ā–¶ļøāœØāŒ›ļø"; - const expectation = "\u2764\u0020\u0020\u0020\u0020\u0020\u0020"; - const result = sanitizeSpecialCharacters(testString); - expect(result).toEqual(expectation); - }); - - it("Should sanitize all German and special characters", () => { - // Test individual German umlaut characters - // Note: lowercase umlauts are converted to uppercase equivalents (Ƥ → AE, not ae) - expect(sanitizeSpecialCharacters("Ƥ")).toEqual("AE"); - expect(sanitizeSpecialCharacters("Ƅ")).toEqual("AE"); - expect(sanitizeSpecialCharacters("ƶ")).toEqual("OE"); - expect(sanitizeSpecialCharacters("Ɩ")).toEqual("OE"); - expect(sanitizeSpecialCharacters("ü")).toEqual("UE"); - expect(sanitizeSpecialCharacters("Ü")).toEqual("UE"); - expect(sanitizeSpecialCharacters("ß")).toEqual("SS"); - - // Test Scandinavian characters - expect(sanitizeSpecialCharacters("Ćø")).toEqual("o"); - expect(sanitizeSpecialCharacters("Ć„")).toEqual("a"); - - // Test ligatures - expect(sanitizeSpecialCharacters("œ")).toEqual("OE"); - expect(sanitizeSpecialCharacters("Ʀ")).toEqual("AE"); - - // Test other accented characters - expect(sanitizeSpecialCharacters("Ƨ")).toEqual("c"); - expect(sanitizeSpecialCharacters("ʒ")).toEqual("f"); - expect(sanitizeSpecialCharacters("µ")).toEqual(" "); // micro sign converts to space - - // Test special punctuation and symbols - expect(sanitizeSpecialCharacters("…")).toEqual("..."); - expect(sanitizeSpecialCharacters("–")).toEqual("-"); - expect(sanitizeSpecialCharacters("⁄")).toEqual("/"); - - // Test full string with all special characters - const allChars = "Ć¤Ć„Ć¶Ć–Ć¼ĆœĆŸĆøĆ„Å“Ć¦Ć§Ę’Āµā€¦ā€“ā„āˆ‘Ā”Ā¶Ā¢[]|{}ā‰ Āæā‚¬Ā®ā€ ĀØĻ€ā€¢Ā±āˆ‚Ā©Āŗāˆ†@Ā„ā‰ˆāˆšāˆ«~āˆž"; - const result = sanitizeSpecialCharacters(allChars); - expect(result).toBeTruthy(); - expect(typeof result).toBe("string"); - // Verify no special characters remain - should only contain A-Z, numbers, and basic punctuation - expect(result).not.toMatch(/[Ć¤Ć„Ć¶Ć–Ć¼ĆœĆŸĆøĆ„Å“Ć¦Ć§Ę’Āµ]/); - }); - - it("Should handle German text with umlauts in context", () => { - const germanText = "Über die Brücke gehen wir für Ɩsterreich"; - const result = sanitizeSpecialCharacters(germanText); - expect(result).toEqual("UEber die BrUEcke gehen wir fUEr OEsterreich"); - }); - - it("Should handle German sharp s (ß) in context", () => { - const germanText = "Straße"; - const result = sanitizeSpecialCharacters(germanText); - expect(result).toEqual("StraSSe"); - }); - - it("Should convert ß to SS in all contexts", () => { - const texts = ["ß", "Straße", "fußball", "groß", "weiß"]; - const expected = ["SS", "StraSSe", "fuSSball", "groSS", "weiSS"]; - - texts.forEach((text, index) => { - const result = sanitizeSpecialCharacters(text); - expect(result).toEqual(expected[index]); - }); - }); -}); diff --git a/src/__tests__/vbml.spec.ts b/src/__tests__/vbml.spec.ts deleted file mode 100644 index 44e3c26..0000000 --- a/src/__tests__/vbml.spec.ts +++ /dev/null @@ -1,700 +0,0 @@ -import { Align, Justify, vbml } from ".."; - -describe("VBML", () => { - it("Should parse a single component on a board", () => { - const result = vbml.parse({ - style: { - height: 1, - width: 2, - }, - components: [ - { - template: "hi", - }, - ], - }); - - expect(result).toEqual([[8, 9]]); - }); - - it("Should layout components side by side", () => { - const result = vbml.parse({ - style: { - height: 1, - width: 4, - }, - components: [ - { - template: "hi", - style: { - width: 2, - height: 1, - }, - }, - { - template: "hi", - style: { - width: 2, - height: 1, - }, - }, - ], - }); - - expect(result).toEqual([[8, 9, 8, 9]]); - }); - it("Should format ƤƄ to aeae", () => { - const result = vbml.parse({ - style: { - height: 1, - width: 4, - }, - components: [ - { - template: "ƤƄ", - style: { - width: 4, - height: 1, - }, - }, - ], - }); - - expect(result).toEqual([[1, 5, 1, 5]]); - }); - - it("Should layout components vertically", () => { - const result = vbml.parse({ - style: { - height: 2, - width: 2, - }, - components: [ - { - template: "hi", - style: { - width: 2, - height: 1, - }, - }, - { - template: "hi", - style: { - width: 2, - height: 1, - }, - }, - ], - }); - - expect(result).toEqual([ - [8, 9], - [8, 9], - ]); - }); - - it("Should flow a third component to the next line", () => { - const result = vbml.parse({ - style: { - height: 2, - width: 4, - }, - components: [ - { - template: "{1}{2}", - style: { - width: 2, - height: 1, - }, - }, - { - template: "{3}{4}", - style: { - width: 2, - height: 1, - }, - }, - { - template: "{5}{6}", - style: { - width: 2, - height: 1, - }, - }, - ], - }); - - expect(result).toEqual([ - [1, 2, 3, 4], - [5, 6, 0, 0], - ]); - }); - - it("Should justify the content vertically", () => { - const result = vbml.parse({ - style: { - height: 5, - width: 1, - }, - components: [ - { - template: "abcd", - style: { - height: 5, - width: 1, - align: Align.justified, - }, - }, - ], - }); - - expect(result).toEqual([[0], [1], [2], [3], [4]]); - }); - - it("Should justify the content vertically with three characters and rows", () => { - const result = vbml.parse({ - style: { - height: 5, - width: 1, - }, - components: [ - { - template: "abc", - style: { - height: 5, - width: 1, - align: Align.justified, - }, - }, - ], - }); - - expect(result).toEqual([[0], [1], [2], [3], [0]]); - }); - - it("Should layout absolute components by relative components", () => { - const result = vbml.parse({ - style: { - height: 22, - width: 6, - }, - components: [ - { - template: "abc", - style: { - height: 6, - width: 22, - align: Align.top, - justify: Justify.left, - }, - }, - { - template: "def", - style: { - height: 1, - width: 3, - align: Align.top, - justify: Justify.left, - absolutePosition: { - x: 3, - y: 0, - }, - }, - }, - ], - }); - expect(result[0]).toEqual([ - 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]); - }); - - it("Should layout absolute components over relative components", () => { - const result = vbml.parse({ - style: { - height: 22, - width: 6, - }, - components: [ - { - template: "abc", - style: { - height: 6, - width: 22, - align: Align.top, - justify: Justify.left, - }, - }, - { - template: "def", - style: { - height: 1, - width: 3, - align: Align.top, - justify: Justify.left, - absolutePosition: { - x: 0, - y: 0, - }, - }, - }, - ], - }); - expect(result[0]).toEqual([ - 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]); - }); - - it("Should layout absolute components over relative components", () => { - const result = vbml.parse({ - style: { - height: 6, - width: 22, - }, - components: [ - { - template: "abc", - style: { - height: 6, - width: 22, - align: Align.top, - justify: Justify.left, - }, - }, - { - template: "def", - style: { - height: 1, - width: 3, - align: Align.top, - justify: Justify.left, - absolutePosition: { - x: 0, - y: 0, - }, - }, - }, - ], - }); - expect(result[0]).toEqual([ - 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]); - }); - - it("Should layout raw components", () => { - const result = vbml.parse({ - style: { - height: 6, - width: 22, - }, - components: [ - { - rawCharacters: [[1, 2, 3]], - }, - ], - }); - expect(result[0]).toEqual([ - 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]); - }); - - it("Should layout absolute components with raw components for a mountain background clock", () => { - const result = vbml.parse({ - props: { - time: "12:00 PM", - }, - style: { - height: 6, - width: 22, - }, - components: [ - { - rawCharacters: [ - [ - 68, 68, 68, 68, 68, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, - ], - [ - 68, 68, 68, 68, 69, 69, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, - 65, 65, 65, 65, 68, 68, - ], - [ - 63, 63, 63, 69, 66, 69, 66, 69, 69, 63, 63, 63, 63, 63, 63, 65, - 65, 65, 65, 65, 65, 63, - ], - [ - 63, 63, 66, 66, 66, 69, 66, 66, 66, 66, 63, 63, 63, 63, 63, 65, - 65, 65, 65, 65, 65, 63, - ], - [ - 64, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, - 65, 65, 65, 65, 64, 64, - ], - [ - 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, - ], - ], - }, - { - template: "{{time}}", - style: { - height: 1, - width: 8, - absolutePosition: { - x: 11, - y: 3, - }, - }, - }, - ], - }); - // visual representation of the result - // https://web.vestaboard.com/board/cda490a3-aaa5-4f2d-9018-a41cd365328a/compose/duplicate/[[68,68,68,68,68,69,69,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68],[68,68,68,68,69,69,69,69,68,68,68,68,68,68,68,68,65,65,65,65,68,68],[63,63,63,69,66,69,66,69,69,63,63,63,63,63,63,65,65,65,65,65,65,63],[63,63,66,66,66,69,66,66,66,66,63,27,28,50,36,36,0,16,13,65,65,63],[64,66,66,66,66,66,66,66,66,66,66,64,64,64,64,64,65,65,65,65,64,64],[66,66,66,66,66,66,66,66,66,66,66,66,64,64,64,64,64,64,64,64,64,64]] - expect(result).toEqual([ - [ - 68, 68, 68, 68, 68, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, - ], - [ - 68, 68, 68, 68, 69, 69, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 65, 65, - 65, 65, 68, 68, - ], - [ - 63, 63, 63, 69, 66, 69, 66, 69, 69, 63, 63, 63, 63, 63, 63, 65, 65, 65, - 65, 65, 65, 63, - ], - [ - 63, 63, 66, 66, 66, 69, 66, 66, 66, 66, 63, 27, 28, 50, 36, 36, 0, 16, - 13, 65, 65, 63, - ], - [ - 64, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, 65, 65, - 65, 65, 64, 64, - ], - [ - 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, - ], - ]); - }); - - it("Should layout a calendar component for Christmas šŸŽ„", () => { - const result = vbml.parse({ - style: { - height: 6, - width: 22, - }, - components: [ - { - calendar: { - defaultDayColor: 66, - month: "12", - year: "2024", - days: { - "25": 63, - }, - }, - style: { - absolutePosition: { - x: 0, - y: 0, - }, - }, - }, - ], - }); - expect(result).toEqual([ - [ - 27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ], - [ - 0, 27, 44, 33, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - [ - 0, 34, 44, 27, 30, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ], - [ - 27, 31, 44, 28, 27, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ], - [ - 28, 28, 44, 28, 34, 66, 66, 66, 63, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ], - [ - 28, 35, 44, 29, 27, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - ]); - expect(result[0].length).toEqual(22); - expect(result[1].length).toEqual(22); - expect(result[2].length).toEqual(22); - expect(result[3].length).toEqual(22); - expect(result[4].length).toEqual(22); - expect(result[5].length).toEqual(22); - }); - - it("Should layout a minimalist calendar component", () => { - const result = vbml.parse({ - style: { - height: 6, - width: 22, - }, - components: [ - { - style: { - absolutePosition: { - x: 0, - y: 0, - }, - }, - calendar: { - defaultDayColor: 66, - hideDates: true, - hideMonthYear: true, - hideSMTWTFS: true, - month: "12", - year: "2024", - days: { - "25": 63, - }, - }, - }, - ], - }); - expect(result).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 63, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - expect(result[0].length).toEqual(22); - expect(result[1].length).toEqual(22); - expect(result[2].length).toEqual(22); - expect(result[3].length).toEqual(22); - expect(result[4].length).toEqual(22); - expect(result[5].length).toEqual(22); - }); - - it("Should layout a calendar component with other components", () => { - const result = vbml.parse({ - style: { - height: 6, - width: 22, - }, - components: [ - { - // absolute position Right - template: "December 2024 Calendar", - style: { - height: 6, - width: 10, - absolutePosition: { - x: 13, - y: 0, - }, - }, - }, - - { - calendar: { - month: "12", - year: "2024", - days: { - "1": 63, - "2": 64, - "3": 65, - "4": 66, - "5": 67, - "6": 68, - "7": 63, - }, - }, - style: { - absolutePosition: { - x: 0, - y: 0, - }, - }, - }, - ], - }); - expect(result).toEqual([ - [ - 27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 4, 5, 3, 5, 13, 2, 5, - 18, 0, - ], - [ - 0, 27, 44, 33, 0, 63, 64, 65, 66, 67, 68, 63, 0, 28, 36, 28, 30, 0, 0, - 0, 0, 0, - ], - [ - 0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 3, 1, 12, 5, 14, 4, 1, - 18, 0, - ], - [ - 27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ], - [ - 28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ], - [ - 28, 35, 44, 29, 27, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - ]); - - expect(result[0].length).toEqual(22); - expect(result[1].length).toEqual(22); - expect(result[2].length).toEqual(22); - expect(result[3].length).toEqual(22); - expect(result[4].length).toEqual(22); - expect(result[5].length).toEqual(22); - }); - - it("Should layout a calendar component on the right", () => { - const result = vbml.parse({ - style: { - height: 6, - width: 22, - }, - components: [ - { - // absolute position Left - template: "Merry Christmas", - style: { - height: 6, - width: 10, - absolutePosition: { - x: 0, - y: 0, - }, - }, - }, - - { - calendar: { - defaultDayColor: 66, - month: "12", - year: "2028", - days: { - "25": 63, - }, - }, - style: { - absolutePosition: { - x: 10, - y: 0, - }, - }, - }, - ], - }); - expect(result).toEqual([ - [ - 13, 5, 18, 18, 25, 0, 0, 0, 0, 0, 27, 28, 59, 28, 34, 19, 13, 20, 23, - 20, 6, 19, - ], - [ - 3, 8, 18, 9, 19, 20, 13, 1, 19, 0, 0, 27, 44, 28, 0, 0, 0, 0, 0, 0, 66, - 66, - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 44, 35, 0, 66, 66, 66, 66, 66, 66, - 66, - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 36, 44, 27, 32, 66, 66, 66, 66, 66, - 66, 66, - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 33, 44, 28, 29, 66, 66, 66, 66, 66, - 66, 66, - ], - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 30, 44, 29, 27, 66, 63, 66, 66, 66, - 66, 66, - ], - ]); - expect(result[0].length).toEqual(22); - expect(result[1].length).toEqual(22); - expect(result[2].length).toEqual(22); - expect(result[3].length).toEqual(22); - expect(result[4].length).toEqual(22); - expect(result[5].length).toEqual(22); - }); - - it("Should respect double returns", () => { - const result = vbml.parse({ - style: { - height: 3, - width: 2, - }, - components: [ - { - template: "h\n\ni", - style: { - align: Align.top, - justify: Justify.left, - }, - }, - ], - }); - - expect(result).toEqual([ - [8, 0], - [0, 0], - [9, 0], - ]); - }); - - it("Should respect triple returns", () => { - const result = vbml.parse({ - style: { - height: 4, - width: 2, - }, - components: [ - { - template: "h\n\n\ni", - style: { - align: Align.top, - justify: Justify.left, - }, - }, - ], - }); - - expect(result).toEqual([ - [8, 0], - [0, 0], - [0, 0], - [9, 0], - ]); - }); - - it("Should let us use random colors", () => { - const result = vbml.parse({ - style: { - height: 1, - width: 1, - }, - components: [ - { - randomColors: { - colors: [61], - }, - }, - ], - }); - - expect(result[0][0]).toBe(61); - }); -}); diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..9208465 --- /dev/null +++ b/test/README.md @@ -0,0 +1,17 @@ +# Shared Test Fixtures + +The `test/input` and `test/expected` trees define the shared behavioral +contract for the Node, Python, and PHP implementations. + +- Keep matching relative paths between `test/input//...` and + `test/expected//...`. +- Use the filename as the test name. +- Store the input payload directly in `test/input`. +- Store either `{ "result": ... }` or `{ "error": { "message": "..." } }` + in `test/expected`. +- Put platform differences under + `test/expected/overrides///...`. +- Every override must include `kind` and `reason`, and then exactly one of + `result`, `error`, or `skip`. +- Prefer shared behavior coverage. Keep local-only tests only when output files + cannot express the contract cleanly, such as deep-copy identity. diff --git a/test/expected/calendar/creates_calendar_with_four_weeks.json b/test/expected/calendar/creates_calendar_with_four_weeks.json new file mode 100644 index 0000000..9e4c3d4 --- /dev/null +++ b/test/expected/calendar/creates_calendar_with_four_weeks.json @@ -0,0 +1,10 @@ +{ + "result": [ + [28, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/calendar/does_not_highlight_days_outside_of_month.json b/test/expected/calendar/does_not_highlight_days_outside_of_month.json new file mode 100644 index 0000000..82e91c2 --- /dev/null +++ b/test/expected/calendar/does_not_highlight_days_outside_of_month.json @@ -0,0 +1,10 @@ +{ + "result": [ + [32, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 34, 44, 29, 36, 65, 65, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/calendar/renders_single_date_header_when_last_week_has_one_day.json b/test/expected/calendar/renders_single_date_header_when_last_week_has_one_day.json new file mode 100644 index 0000000..0ab7723 --- /dev/null +++ b/test/expected/calendar/renders_single_date_header_when_last_week_has_one_day.json @@ -0,0 +1,10 @@ +{ + "result": [ + [28, 59, 28, 33, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 34, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/characterCodesToAscii/converts_color_codes.json b/test/expected/characterCodesToAscii/converts_color_codes.json new file mode 100644 index 0000000..a4de753 --- /dev/null +++ b/test/expected/characterCodesToAscii/converts_color_codes.json @@ -0,0 +1,3 @@ +{ + "result": "šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›" +} diff --git a/test/expected/characterCodesToAscii/handles_multiple_rows.json b/test/expected/characterCodesToAscii/handles_multiple_rows.json new file mode 100644 index 0000000..d1b8001 --- /dev/null +++ b/test/expected/characterCodesToAscii/handles_multiple_rows.json @@ -0,0 +1,3 @@ +{ + "result": "🟄🟧\n\n🟄🟧" +} diff --git a/test/expected/characterCodesToAscii/spaces_out_letters.json b/test/expected/characterCodesToAscii/spaces_out_letters.json new file mode 100644 index 0000000..ec82794 --- /dev/null +++ b/test/expected/characterCodesToAscii/spaces_out_letters.json @@ -0,0 +1,3 @@ +{ + "result": "A B " +} diff --git a/test/expected/characterCodesToString/converts_two_line_sentence.json b/test/expected/characterCodesToString/converts_two_line_sentence.json new file mode 100644 index 0000000..f361ed0 --- /dev/null +++ b/test/expected/characterCodesToString/converts_two_line_sentence.json @@ -0,0 +1,3 @@ +{ + "result": "THIS IS A LONGER BLOCK THAT SPANS 2 LINES" +} diff --git a/test/expected/characterCodesToString/converts_word_to_string.json b/test/expected/characterCodesToString/converts_word_to_string.json new file mode 100644 index 0000000..d0b482d --- /dev/null +++ b/test/expected/characterCodesToString/converts_word_to_string.json @@ -0,0 +1,3 @@ +{ + "result": "AB" +} diff --git a/test/expected/characterCodesToString/does_not_insert_line_break_when_first_word_fits.json b/test/expected/characterCodesToString/does_not_insert_line_break_when_first_word_fits.json new file mode 100644 index 0000000..32a7af8 --- /dev/null +++ b/test/expected/characterCodesToString/does_not_insert_line_break_when_first_word_fits.json @@ -0,0 +1,3 @@ +{ + "result": "A B" +} diff --git a/test/expected/characterCodesToString/handles_breaks.json b/test/expected/characterCodesToString/handles_breaks.json new file mode 100644 index 0000000..2b91fed --- /dev/null +++ b/test/expected/characterCodesToString/handles_breaks.json @@ -0,0 +1,3 @@ +{ + "result": "HANDLE BREAKS GRACEFULLY" +} diff --git a/test/expected/characterCodesToString/handles_line_breaks.json b/test/expected/characterCodesToString/handles_line_breaks.json new file mode 100644 index 0000000..fa5a5cb --- /dev/null +++ b/test/expected/characterCodesToString/handles_line_breaks.json @@ -0,0 +1,3 @@ +{ + "result": "AB\nCD" +} diff --git a/test/expected/classic/converts_ae_umlaut.json b/test/expected/classic/converts_ae_umlaut.json new file mode 100644 index 0000000..acf0563 --- /dev/null +++ b/test/expected/classic/converts_ae_umlaut.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_double_newline.json b/test/expected/classic/converts_double_newline.json new file mode 100644 index 0000000..1d71575 --- /dev/null +++ b/test/expected/classic/converts_double_newline.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_embedded_char_code_string.json b/test/expected/classic/converts_embedded_char_code_string.json new file mode 100644 index 0000000..88f9e7a --- /dev/null +++ b/test/expected/classic/converts_embedded_char_code_string.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 8, 5, 12, 12, 15, 55, 0, 23, 15, 18, 12, 4, 37, 0, 29, 33, 0, 0, 0, 0, 0], + [0, 65, 32, 32, 65, 0, 34, 35, 65, 0, 65, 28, 36, 0, 8, 5, 12, 12, 15, 0, 0, 0], + [0, 23, 15, 18, 12, 4, 0, 23, 8, 1, 20, 19, 21, 16, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_emoji_colors.json b/test/expected/classic/converts_emoji_colors.json new file mode 100644 index 0000000..b62a9a5 --- /dev/null +++ b/test/expected/classic/converts_emoji_colors.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 63, 64, 65, 66, 67, 68, 69, 70, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_hyphen_string.json b/test/expected/classic/converts_hyphen_string.json new file mode 100644 index 0000000..fd04895 --- /dev/null +++ b/test/expected/classic/converts_hyphen_string.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 44, 0, 44, 8, 25, 16, 8, 5, 14, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_long_string_with_digits.json b/test/expected/classic/converts_long_string_with_digits.json new file mode 100644 index 0000000..abbbdef --- /dev/null +++ b/test/expected/classic/converts_long_string_with_digits.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [18, 5, 1, 12, 12, 25, 12, 15, 14, 7, 23, 15, 18, 4, 20, 8, 1, 20, 9, 19, 13, 15], + [18, 5, 20, 8, 1, 14, 28, 28, 3, 8, 1, 18, 3, 20, 5, 18, 19, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_longer_string.json b/test/expected/classic/converts_longer_string.json new file mode 100644 index 0000000..f6ac086 --- /dev/null +++ b/test/expected/classic/converts_longer_string.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 18, 5, 1, 12, 12, 25, 12, 15, 14, 7, 23, 15, 18, 4, 20, 8, 1, 20, 0, 0, 0], + [0, 9, 19, 13, 15, 18, 5, 20, 8, 1, 14, 20, 23, 5, 14, 20, 25, 20, 23, 0, 0, 0], + [0, 15, 3, 8, 1, 18, 3, 20, 5, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_single_char_code.json b/test/expected/classic/converts_single_char_code.json new file mode 100644 index 0000000..5965e94 --- /dev/null +++ b/test/expected/classic/converts_single_char_code.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_single_newline.json b/test/expected/classic/converts_single_newline.json new file mode 100644 index 0000000..5e4566a --- /dev/null +++ b/test/expected/classic/converts_single_newline.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_special_characters.json b/test/expected/classic/converts_special_characters.json new file mode 100644 index 0000000..7267c3f --- /dev/null +++ b/test/expected/classic/converts_special_characters.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [37, 38, 39, 40, 54, 0, 47, 0, 41, 42, 46, 1, 19, 19, 0, 6, 0, 0, 0, 0, 0, 0], + [0, 3, 0, 0, 0, 0, 0, 0, 41, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/converts_string_to_classic_board.json b/test/expected/classic/converts_string_to_classic_board.json new file mode 100644 index 0000000..2c8b9a3 --- /dev/null +++ b/test/expected/classic/converts_string_to_classic_board.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 8, 5, 12, 12, 15, 55, 0, 23, 15, 18, 12, 4, 37, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/preserves_triple_spaces.json b/test/expected/classic/preserves_triple_spaces.json new file mode 100644 index 0000000..4ec299d --- /dev/null +++ b/test/expected/classic/preserves_triple_spaces.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/respects_double_spaces.json b/test/expected/classic/respects_double_spaces.json new file mode 100644 index 0000000..a8b5c7a --- /dev/null +++ b/test/expected/classic/respects_double_spaces.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/classic/returns_empty_board_for_empty_string.json b/test/expected/classic/returns_empty_board_for_empty_string.json new file mode 100644 index 0000000..519d99a --- /dev/null +++ b/test/expected/classic/returns_empty_board_for_empty_string.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/hasSpecialCharacters/excludes_black_color_swatch.json b/test/expected/hasSpecialCharacters/excludes_black_color_swatch.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/excludes_black_color_swatch.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/excludes_ios_double_quote.json b/test/expected/hasSpecialCharacters/excludes_ios_double_quote.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/excludes_ios_double_quote.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/excludes_ios_single_quote.json b/test/expected/hasSpecialCharacters/excludes_ios_single_quote.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/excludes_ios_single_quote.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/excludes_newlines.json b/test/expected/hasSpecialCharacters/excludes_newlines.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/excludes_newlines.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/excludes_orange_color_swatch.json b/test/expected/hasSpecialCharacters/excludes_orange_color_swatch.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/excludes_orange_color_swatch.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/excludes_white_color_swatch.json b/test/expected/hasSpecialCharacters/excludes_white_color_swatch.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/excludes_white_color_swatch.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/includes_fractions.json b/test/expected/hasSpecialCharacters/includes_fractions.json new file mode 100644 index 0000000..8c87d25 --- /dev/null +++ b/test/expected/hasSpecialCharacters/includes_fractions.json @@ -0,0 +1,3 @@ +{ + "result": true +} diff --git a/test/expected/hasSpecialCharacters/returns_false_for_empty_string.json b/test/expected/hasSpecialCharacters/returns_false_for_empty_string.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/returns_false_for_empty_string.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/returns_false_for_lowercase_alphabet.json b/test/expected/hasSpecialCharacters/returns_false_for_lowercase_alphabet.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/returns_false_for_lowercase_alphabet.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/returns_false_for_numbers.json b/test/expected/hasSpecialCharacters/returns_false_for_numbers.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/returns_false_for_numbers.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/returns_false_for_supported_symbols.json b/test/expected/hasSpecialCharacters/returns_false_for_supported_symbols.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/returns_false_for_supported_symbols.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/returns_false_for_uppercase_alphabet.json b/test/expected/hasSpecialCharacters/returns_false_for_uppercase_alphabet.json new file mode 100644 index 0000000..8d7cc52 --- /dev/null +++ b/test/expected/hasSpecialCharacters/returns_false_for_uppercase_alphabet.json @@ -0,0 +1,3 @@ +{ + "result": false +} diff --git a/test/expected/hasSpecialCharacters/returns_true_for_mixed_special_and_standard_characters.json b/test/expected/hasSpecialCharacters/returns_true_for_mixed_special_and_standard_characters.json new file mode 100644 index 0000000..8c87d25 --- /dev/null +++ b/test/expected/hasSpecialCharacters/returns_true_for_mixed_special_and_standard_characters.json @@ -0,0 +1,3 @@ +{ + "result": true +} diff --git a/test/expected/hasSpecialCharacters/returns_true_for_special_character.json b/test/expected/hasSpecialCharacters/returns_true_for_special_character.json new file mode 100644 index 0000000..8c87d25 --- /dev/null +++ b/test/expected/hasSpecialCharacters/returns_true_for_special_character.json @@ -0,0 +1,3 @@ +{ + "result": true +} diff --git a/test/expected/parseComponent/adds_extra_spaces.json b/test/expected/parseComponent/adds_extra_spaces.json new file mode 100644 index 0000000..dac64ae --- /dev/null +++ b/test/expected/parseComponent/adds_extra_spaces.json @@ -0,0 +1,5 @@ +{ + "result": [ + [8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37, 0] + ] +} diff --git a/test/expected/parseComponent/adds_template_props.json b/test/expected/parseComponent/adds_template_props.json new file mode 100644 index 0000000..ed75b71 --- /dev/null +++ b/test/expected/parseComponent/adds_template_props.json @@ -0,0 +1,5 @@ +{ + "result": [ + [8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4] + ] +} diff --git a/test/expected/parseComponent/allows_array_iteration.json b/test/expected/parseComponent/allows_array_iteration.json new file mode 100644 index 0000000..33a7508 --- /dev/null +++ b/test/expected/parseComponent/allows_array_iteration.json @@ -0,0 +1,5 @@ +{ + "result": [ + [27, 28, 29] + ] +} diff --git a/test/expected/parseComponent/allows_conditions_when_false.json b/test/expected/parseComponent/allows_conditions_when_false.json new file mode 100644 index 0000000..0412df3 --- /dev/null +++ b/test/expected/parseComponent/allows_conditions_when_false.json @@ -0,0 +1,5 @@ +{ + "result": [ + [9, 0, 1, 13, 0, 13, 1, 4, 0, 0] + ] +} diff --git a/test/expected/parseComponent/allows_conditions_when_true.json b/test/expected/parseComponent/allows_conditions_when_true.json new file mode 100644 index 0000000..75770e0 --- /dev/null +++ b/test/expected/parseComponent/allows_conditions_when_true.json @@ -0,0 +1,5 @@ +{ + "result": [ + [9, 0, 1, 13, 0, 8, 1, 16, 16, 25] + ] +} diff --git a/test/expected/parseComponent/allows_newlines.json b/test/expected/parseComponent/allows_newlines.json new file mode 100644 index 0000000..90bae0b --- /dev/null +++ b/test/expected/parseComponent/allows_newlines.json @@ -0,0 +1,6 @@ +{ + "result": [ + [1, 0], + [1, 0] + ] +} diff --git a/test/expected/parseComponent/allows_newlines_after_spaces.json b/test/expected/parseComponent/allows_newlines_after_spaces.json new file mode 100644 index 0000000..90bae0b --- /dev/null +++ b/test/expected/parseComponent/allows_newlines_after_spaces.json @@ -0,0 +1,6 @@ +{ + "result": [ + [1, 0], + [1, 0] + ] +} diff --git a/test/expected/parseComponent/allows_newlines_before_spaces.json b/test/expected/parseComponent/allows_newlines_before_spaces.json new file mode 100644 index 0000000..baaa103 --- /dev/null +++ b/test/expected/parseComponent/allows_newlines_before_spaces.json @@ -0,0 +1,6 @@ +{ + "result": [ + [1, 0], + [70, 1] + ] +} diff --git a/test/expected/parseComponent/automatically_breaks_line.json b/test/expected/parseComponent/automatically_breaks_line.json new file mode 100644 index 0000000..71af68f --- /dev/null +++ b/test/expected/parseComponent/automatically_breaks_line.json @@ -0,0 +1,6 @@ +{ + "result": [ + [8, 5, 12, 12, 15, 0], + [23, 15, 18, 12, 4, 37] + ] +} diff --git a/test/expected/parseComponent/breaks_on_lines_with_character_codes.json b/test/expected/parseComponent/breaks_on_lines_with_character_codes.json new file mode 100644 index 0000000..97ebf7c --- /dev/null +++ b/test/expected/parseComponent/breaks_on_lines_with_character_codes.json @@ -0,0 +1,6 @@ +{ + "result": [ + [1, 2, 0], + [3, 4, 0] + ] +} diff --git a/test/expected/parseComponent/converts_emoji_characters_to_character_codes.json b/test/expected/parseComponent/converts_emoji_characters_to_character_codes.json new file mode 100644 index 0000000..20246c1 --- /dev/null +++ b/test/expected/parseComponent/converts_emoji_characters_to_character_codes.json @@ -0,0 +1,5 @@ +{ + "result": [ + [63, 64, 65, 66, 67, 68, 69, 70] + ] +} diff --git a/test/expected/parseComponent/does_not_break_when_unnecessary.json b/test/expected/parseComponent/does_not_break_when_unnecessary.json new file mode 100644 index 0000000..e431460 --- /dev/null +++ b/test/expected/parseComponent/does_not_break_when_unnecessary.json @@ -0,0 +1,7 @@ +{ + "result": [ + [8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/parseComponent/formats_longer_message_centered.json b/test/expected/parseComponent/formats_longer_message_centered.json new file mode 100644 index 0000000..fd7ceb1 --- /dev/null +++ b/test/expected/parseComponent/formats_longer_message_centered.json @@ -0,0 +1,6 @@ +{ + "result": [ + [0, 20, 8, 1, 14, 11, 0, 25, 15, 21, 0, 6, 15, 18, 0, 8, 1, 22, 9, 14, 7, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 19, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/parseComponent/formats_longer_plain_text.json b/test/expected/parseComponent/formats_longer_plain_text.json new file mode 100644 index 0000000..18adfe2 --- /dev/null +++ b/test/expected/parseComponent/formats_longer_plain_text.json @@ -0,0 +1,6 @@ +{ + "result": [ + [20, 8, 1, 14, 11, 0, 25, 15, 21, 0, 0, 0], + [6, 15, 18, 0, 8, 1, 22, 9, 14, 7, 0, 0] + ] +} diff --git a/test/expected/parseComponent/formats_plain_text.json b/test/expected/parseComponent/formats_plain_text.json new file mode 100644 index 0000000..ae0e7e4 --- /dev/null +++ b/test/expected/parseComponent/formats_plain_text.json @@ -0,0 +1,5 @@ +{ + "result": [ + [8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37] + ] +} diff --git a/test/expected/parseComponent/horizontally_aligns_center.json b/test/expected/parseComponent/horizontally_aligns_center.json new file mode 100644 index 0000000..c6515cd --- /dev/null +++ b/test/expected/parseComponent/horizontally_aligns_center.json @@ -0,0 +1,5 @@ +{ + "result": [ + [0, 37, 0] + ] +} diff --git a/test/expected/parseComponent/horizontally_aligns_justified.json b/test/expected/parseComponent/horizontally_aligns_justified.json new file mode 100644 index 0000000..2b634ae --- /dev/null +++ b/test/expected/parseComponent/horizontally_aligns_justified.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 20, 5, 19, 20, 9, 14, 7, 0, 20, 5, 19, 20, 9, 14, 7, 0, 27, 28, 29, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/parseComponent/horizontally_aligns_right.json b/test/expected/parseComponent/horizontally_aligns_right.json new file mode 100644 index 0000000..6255d77 --- /dev/null +++ b/test/expected/parseComponent/horizontally_aligns_right.json @@ -0,0 +1,5 @@ +{ + "result": [ + [0, 0, 37] + ] +} diff --git a/test/expected/parseComponent/horizontally_and_vertically_aligned_center.json b/test/expected/parseComponent/horizontally_and_vertically_aligned_center.json new file mode 100644 index 0000000..e9e57fd --- /dev/null +++ b/test/expected/parseComponent/horizontally_and_vertically_aligned_center.json @@ -0,0 +1,7 @@ +{ + "result": [ + [0, 0, 0], + [0, 37, 0], + [0, 0, 0] + ] +} diff --git a/test/expected/parseComponent/justified_long_complex_message.json b/test/expected/parseComponent/justified_long_complex_message.json new file mode 100644 index 0000000..9011371 --- /dev/null +++ b/test/expected/parseComponent/justified_long_complex_message.json @@ -0,0 +1,10 @@ +{ + "result": [ + [16, 1, 3, 11, 0, 13, 25, 0, 2, 15, 24, 0, 23, 9, 20, 8, 0, 6, 9, 22, 5, 0], + [4, 15, 26, 5, 14, 0, 12, 9, 17, 21, 15, 18, 0, 10, 21, 7, 19, 56, 0, 20, 8, 5], + [17, 21, 9, 3, 11, 0, 2, 18, 15, 23, 14, 0, 6, 15, 24, 0, 10, 21, 13, 16, 19, 0], + [15, 22, 5, 18, 0, 20, 8, 5, 0, 12, 1, 26, 25, 0, 4, 15, 7, 56, 0, 8, 15, 23], + [22, 5, 24, 9, 14, 7, 12, 25, 0, 17, 21, 9, 3, 11, 0, 4, 1, 6, 20, 0, 0, 0], + [26, 5, 2, 18, 1, 19, 0, 10, 21, 13, 16, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/parseComponent/justified_when_flowing_to_next_line.json b/test/expected/parseComponent/justified_when_flowing_to_next_line.json new file mode 100644 index 0000000..02e8495 --- /dev/null +++ b/test/expected/parseComponent/justified_when_flowing_to_next_line.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 20, 5, 19, 20, 9, 14, 7, 0, 20, 5, 19, 20, 9, 14, 7, 0, 0, 0, 0], + [0, 0, 0, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/parseComponent/justified_when_full_line_covered.json b/test/expected/parseComponent/justified_when_full_line_covered.json new file mode 100644 index 0000000..0edca0a --- /dev/null +++ b/test/expected/parseComponent/justified_when_full_line_covered.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [20, 5, 19, 20, 9, 14, 7, 0, 20, 5, 19, 20, 9, 14, 7, 0, 27, 28, 29, 30, 31, 32], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/parseComponent/parses_absolute_component.json b/test/expected/parseComponent/parses_absolute_component.json new file mode 100644 index 0000000..344633a --- /dev/null +++ b/test/expected/parseComponent/parses_absolute_component.json @@ -0,0 +1,10 @@ +{ + "result": { + "characters": [ + [8, 5, 12, 12, 15, 0], + [23, 15, 18, 12, 4, 37] + ], + "x": 4, + "y": 2 + } +} diff --git a/test/expected/parseComponent/parses_character_codes.json b/test/expected/parseComponent/parses_character_codes.json new file mode 100644 index 0000000..889aacc --- /dev/null +++ b/test/expected/parseComponent/parses_character_codes.json @@ -0,0 +1,5 @@ +{ + "result": [ + [1, 2, 3] + ] +} diff --git a/test/expected/parseComponent/parses_raw_component.json b/test/expected/parseComponent/parses_raw_component.json new file mode 100644 index 0000000..3f189a6 --- /dev/null +++ b/test/expected/parseComponent/parses_raw_component.json @@ -0,0 +1,6 @@ +{ + "result": [ + [1, 2], + [3, 4] + ] +} diff --git a/test/expected/parseComponent/parses_two_digit_character_codes.json b/test/expected/parseComponent/parses_two_digit_character_codes.json new file mode 100644 index 0000000..3108498 --- /dev/null +++ b/test/expected/parseComponent/parses_two_digit_character_codes.json @@ -0,0 +1,5 @@ +{ + "result": [ + [68, 69] + ] +} diff --git a/test/expected/parseComponent/splits_long_words.json b/test/expected/parseComponent/splits_long_words.json new file mode 100644 index 0000000..3f189a6 --- /dev/null +++ b/test/expected/parseComponent/splits_long_words.json @@ -0,0 +1,6 @@ +{ + "result": [ + [1, 2], + [3, 4] + ] +} diff --git a/test/expected/parseComponent/throws_for_invalid_character_codes.json b/test/expected/parseComponent/throws_for_invalid_character_codes.json new file mode 100644 index 0000000..1428417 --- /dev/null +++ b/test/expected/parseComponent/throws_for_invalid_character_codes.json @@ -0,0 +1,5 @@ +{ + "error": { + "message": "Invalid Character Code: 99" + } +} diff --git a/test/expected/parseComponent/vertically_aligns_bottom.json b/test/expected/parseComponent/vertically_aligns_bottom.json new file mode 100644 index 0000000..f0083ce --- /dev/null +++ b/test/expected/parseComponent/vertically_aligns_bottom.json @@ -0,0 +1,8 @@ +{ + "result": [ + [0], + [0], + [0], + [37] + ] +} diff --git a/test/expected/parseComponent/vertically_aligns_center.json b/test/expected/parseComponent/vertically_aligns_center.json new file mode 100644 index 0000000..ec445af --- /dev/null +++ b/test/expected/parseComponent/vertically_aligns_center.json @@ -0,0 +1,7 @@ +{ + "result": [ + [0], + [37], + [0] + ] +} diff --git a/test/expected/parseComponent/vertically_aligns_center_multiple_rows.json b/test/expected/parseComponent/vertically_aligns_center_multiple_rows.json new file mode 100644 index 0000000..0b025c6 --- /dev/null +++ b/test/expected/parseComponent/vertically_aligns_center_multiple_rows.json @@ -0,0 +1,9 @@ +{ + "result": [ + [0], + [0], + [37], + [0], + [0] + ] +} diff --git a/test/expected/parseComponent/vertically_aligns_center_sticks_to_top_when_no_even_padding.json b/test/expected/parseComponent/vertically_aligns_center_sticks_to_top_when_no_even_padding.json new file mode 100644 index 0000000..c31603f --- /dev/null +++ b/test/expected/parseComponent/vertically_aligns_center_sticks_to_top_when_no_even_padding.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0], + [0], + [37], + [0], + [0], + [0] + ] +} diff --git a/test/expected/sanitizeSpecialCharacters/accepts_whitespace_after_heart.json b/test/expected/sanitizeSpecialCharacters/accepts_whitespace_after_heart.json new file mode 100644 index 0000000..ee30438 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/accepts_whitespace_after_heart.json @@ -0,0 +1,3 @@ +{ + "result": "ā¤ " +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_cedilla.json b/test/expected/sanitizeSpecialCharacters/converts_cedilla.json new file mode 100644 index 0000000..11cc057 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_cedilla.json @@ -0,0 +1,3 @@ +{ + "result": "c" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_ellipsis.json b/test/expected/sanitizeSpecialCharacters/converts_ellipsis.json new file mode 100644 index 0000000..92b9c3e --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_ellipsis.json @@ -0,0 +1,3 @@ +{ + "result": "..." +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_en_dash.json b/test/expected/sanitizeSpecialCharacters/converts_en_dash.json new file mode 100644 index 0000000..752df03 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_en_dash.json @@ -0,0 +1,3 @@ +{ + "result": "-" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_f_hook.json b/test/expected/sanitizeSpecialCharacters/converts_f_hook.json new file mode 100644 index 0000000..b5d927d --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_f_hook.json @@ -0,0 +1,3 @@ +{ + "result": "f" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_fraction_slash.json b/test/expected/sanitizeSpecialCharacters/converts_fraction_slash.json new file mode 100644 index 0000000..f83c838 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_fraction_slash.json @@ -0,0 +1,3 @@ +{ + "result": "/" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_ae.json b/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_ae.json new file mode 100644 index 0000000..a73c950 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_ae.json @@ -0,0 +1,3 @@ +{ + "result": "AE" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_oe.json b/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_oe.json new file mode 100644 index 0000000..a4d2025 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_oe.json @@ -0,0 +1,3 @@ +{ + "result": "OE" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_ue.json b/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_ue.json new file mode 100644 index 0000000..e4dc4ac --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_german_lowercase_ue.json @@ -0,0 +1,3 @@ +{ + "result": "UE" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_ae.json b/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_ae.json new file mode 100644 index 0000000..a73c950 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_ae.json @@ -0,0 +1,3 @@ +{ + "result": "AE" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_oe.json b/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_oe.json new file mode 100644 index 0000000..a4d2025 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_oe.json @@ -0,0 +1,3 @@ +{ + "result": "OE" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_ue.json b/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_ue.json new file mode 100644 index 0000000..e4dc4ac --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_german_uppercase_ue.json @@ -0,0 +1,3 @@ +{ + "result": "UE" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_ligature_ae.json b/test/expected/sanitizeSpecialCharacters/converts_ligature_ae.json new file mode 100644 index 0000000..a73c950 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_ligature_ae.json @@ -0,0 +1,3 @@ +{ + "result": "AE" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_ligature_oe.json b/test/expected/sanitizeSpecialCharacters/converts_ligature_oe.json new file mode 100644 index 0000000..a4d2025 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_ligature_oe.json @@ -0,0 +1,3 @@ +{ + "result": "OE" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_micro_sign_to_space.json b/test/expected/sanitizeSpecialCharacters/converts_micro_sign_to_space.json new file mode 100644 index 0000000..07dc643 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_micro_sign_to_space.json @@ -0,0 +1,3 @@ +{ + "result": " " +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_scandinavian_a_ring.json b/test/expected/sanitizeSpecialCharacters/converts_scandinavian_a_ring.json new file mode 100644 index 0000000..11ca3ce --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_scandinavian_a_ring.json @@ -0,0 +1,3 @@ +{ + "result": "a" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_scandinavian_o_stroke.json b/test/expected/sanitizeSpecialCharacters/converts_scandinavian_o_stroke.json new file mode 100644 index 0000000..c037c06 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_scandinavian_o_stroke.json @@ -0,0 +1,3 @@ +{ + "result": "o" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_sharp_s.json b/test/expected/sanitizeSpecialCharacters/converts_sharp_s.json new file mode 100644 index 0000000..cfa6e8f --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_sharp_s.json @@ -0,0 +1,3 @@ +{ + "result": "SS" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_fussball.json b/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_fussball.json new file mode 100644 index 0000000..7523116 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_fussball.json @@ -0,0 +1,3 @@ +{ + "result": "fuSSball" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_gross.json b/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_gross.json new file mode 100644 index 0000000..cab3cee --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_gross.json @@ -0,0 +1,3 @@ +{ + "result": "groSS" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_weiss.json b/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_weiss.json new file mode 100644 index 0000000..b1f18ea --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_sharp_s_in_weiss.json @@ -0,0 +1,3 @@ +{ + "result": "weiSS" +} diff --git a/test/expected/sanitizeSpecialCharacters/converts_unsupported_sequenced_emojis_to_whitespace.json b/test/expected/sanitizeSpecialCharacters/converts_unsupported_sequenced_emojis_to_whitespace.json new file mode 100644 index 0000000..aadacfe --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/converts_unsupported_sequenced_emojis_to_whitespace.json @@ -0,0 +1,3 @@ +{ + "result": " " +} diff --git a/test/expected/sanitizeSpecialCharacters/does_not_modify_plain_text.json b/test/expected/sanitizeSpecialCharacters/does_not_modify_plain_text.json new file mode 100644 index 0000000..ee284fc --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/does_not_modify_plain_text.json @@ -0,0 +1,3 @@ +{ + "result": "abcdefghijklmnopqrstuvwxyz" +} diff --git a/test/expected/sanitizeSpecialCharacters/does_not_replace_vestaboard_heart.json b/test/expected/sanitizeSpecialCharacters/does_not_replace_vestaboard_heart.json new file mode 100644 index 0000000..c77c79c --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/does_not_replace_vestaboard_heart.json @@ -0,0 +1,3 @@ +{ + "result": "ā¤" +} diff --git a/test/expected/sanitizeSpecialCharacters/handles_german_sharp_s_in_context.json b/test/expected/sanitizeSpecialCharacters/handles_german_sharp_s_in_context.json new file mode 100644 index 0000000..95227b5 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/handles_german_sharp_s_in_context.json @@ -0,0 +1,3 @@ +{ + "result": "StraSSe" +} diff --git a/test/expected/sanitizeSpecialCharacters/handles_german_text_with_umlauts_in_context.json b/test/expected/sanitizeSpecialCharacters/handles_german_text_with_umlauts_in_context.json new file mode 100644 index 0000000..cd34849 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/handles_german_text_with_umlauts_in_context.json @@ -0,0 +1,3 @@ +{ + "result": "UEber die BrUEcke gehen wir fUEr OEsterreich" +} diff --git a/test/expected/sanitizeSpecialCharacters/handles_heart_emoji_and_unsupported_emojis.json b/test/expected/sanitizeSpecialCharacters/handles_heart_emoji_and_unsupported_emojis.json new file mode 100644 index 0000000..bb6adfd --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/handles_heart_emoji_and_unsupported_emojis.json @@ -0,0 +1,3 @@ +{ + "result": "ā¤ " +} diff --git a/test/expected/sanitizeSpecialCharacters/handles_mixed_special_characters.json b/test/expected/sanitizeSpecialCharacters/handles_mixed_special_characters.json new file mode 100644 index 0000000..c5c60bd --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/handles_mixed_special_characters.json @@ -0,0 +1,3 @@ +{ + "result": "hello world" +} diff --git a/test/expected/sanitizeSpecialCharacters/handles_multiple_special_characters_together.json b/test/expected/sanitizeSpecialCharacters/handles_multiple_special_characters_together.json new file mode 100644 index 0000000..b0dad5a --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/handles_multiple_special_characters_together.json @@ -0,0 +1,3 @@ +{ + "result": "ei" +} diff --git a/test/expected/sanitizeSpecialCharacters/handles_sentence.json b/test/expected/sanitizeSpecialCharacters/handles_sentence.json new file mode 100644 index 0000000..c5c60bd --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/handles_sentence.json @@ -0,0 +1,3 @@ +{ + "result": "hello world" +} diff --git a/test/expected/sanitizeSpecialCharacters/preserves_space_between_heart_and_emoji.json b/test/expected/sanitizeSpecialCharacters/preserves_space_between_heart_and_emoji.json new file mode 100644 index 0000000..3f4f954 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/preserves_space_between_heart_and_emoji.json @@ -0,0 +1,3 @@ +{ + "result": "ā¤ 🟧" +} diff --git a/test/expected/sanitizeSpecialCharacters/preserves_space_between_heart_and_latin_glyph.json b/test/expected/sanitizeSpecialCharacters/preserves_space_between_heart_and_latin_glyph.json new file mode 100644 index 0000000..7c3114b --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/preserves_space_between_heart_and_latin_glyph.json @@ -0,0 +1,3 @@ +{ + "result": "ā¤ A" +} diff --git a/test/expected/sanitizeSpecialCharacters/preserves_spaces_between_hearts.json b/test/expected/sanitizeSpecialCharacters/preserves_spaces_between_hearts.json new file mode 100644 index 0000000..83051ea --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/preserves_spaces_between_hearts.json @@ -0,0 +1,3 @@ +{ + "result": "ā¤ ā¤ ā¤ ā¤ ā¤" +} diff --git a/test/expected/sanitizeSpecialCharacters/replaces_accented_character.json b/test/expected/sanitizeSpecialCharacters/replaces_accented_character.json new file mode 100644 index 0000000..11ca3ce --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/replaces_accented_character.json @@ -0,0 +1,3 @@ +{ + "result": "a" +} diff --git a/test/expected/sanitizeSpecialCharacters/replaces_fractions_with_multiple_characters.json b/test/expected/sanitizeSpecialCharacters/replaces_fractions_with_multiple_characters.json new file mode 100644 index 0000000..e3e7fe9 --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/replaces_fractions_with_multiple_characters.json @@ -0,0 +1,3 @@ +{ + "result": "1/2" +} diff --git a/test/expected/sanitizeSpecialCharacters/sanitizes_variation_selector_from_heart_emoji.json b/test/expected/sanitizeSpecialCharacters/sanitizes_variation_selector_from_heart_emoji.json new file mode 100644 index 0000000..c77c79c --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/sanitizes_variation_selector_from_heart_emoji.json @@ -0,0 +1,3 @@ +{ + "result": "ā¤" +} diff --git a/test/expected/sanitizeSpecialCharacters/sanitizes_variation_selector_from_unicode_literal.json b/test/expected/sanitizeSpecialCharacters/sanitizes_variation_selector_from_unicode_literal.json new file mode 100644 index 0000000..c77c79c --- /dev/null +++ b/test/expected/sanitizeSpecialCharacters/sanitizes_variation_selector_from_unicode_literal.json @@ -0,0 +1,3 @@ +{ + "result": "ā¤" +} diff --git a/test/expected/vbml/flows_third_component_to_next_line.json b/test/expected/vbml/flows_third_component_to_next_line.json new file mode 100644 index 0000000..2915293 --- /dev/null +++ b/test/expected/vbml/flows_third_component_to_next_line.json @@ -0,0 +1,6 @@ +{ + "result": [ + [1, 2, 3, 4], + [5, 6, 0, 0] + ] +} diff --git a/test/expected/vbml/formats_ae_umlaut.json b/test/expected/vbml/formats_ae_umlaut.json new file mode 100644 index 0000000..3132adf --- /dev/null +++ b/test/expected/vbml/formats_ae_umlaut.json @@ -0,0 +1,5 @@ +{ + "result": [ + [1, 5, 1, 5] + ] +} diff --git a/test/expected/vbml/justifies_content_vertically.json b/test/expected/vbml/justifies_content_vertically.json new file mode 100644 index 0000000..714c94a --- /dev/null +++ b/test/expected/vbml/justifies_content_vertically.json @@ -0,0 +1,9 @@ +{ + "result": [ + [0], + [1], + [2], + [3], + [4] + ] +} diff --git a/test/expected/vbml/justifies_content_vertically_three_chars.json b/test/expected/vbml/justifies_content_vertically_three_chars.json new file mode 100644 index 0000000..1c0a3d7 --- /dev/null +++ b/test/expected/vbml/justifies_content_vertically_three_chars.json @@ -0,0 +1,9 @@ +{ + "result": [ + [0], + [1], + [2], + [3], + [0] + ] +} diff --git a/test/expected/vbml/layouts_absolute_components_by_relative_components.json b/test/expected/vbml/layouts_absolute_components_by_relative_components.json new file mode 100644 index 0000000..8d2649d --- /dev/null +++ b/test/expected/vbml/layouts_absolute_components_by_relative_components.json @@ -0,0 +1,26 @@ +{ + "result": [ + [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/layouts_absolute_components_with_raw_components_for_mountain_clock.json b/test/expected/vbml/layouts_absolute_components_with_raw_components_for_mountain_clock.json new file mode 100644 index 0000000..d877c63 --- /dev/null +++ b/test/expected/vbml/layouts_absolute_components_with_raw_components_for_mountain_clock.json @@ -0,0 +1,10 @@ +{ + "result": [ + [68, 68, 68, 68, 68, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68], + [68, 68, 68, 68, 69, 69, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 65, 65, 65, 65, 68, 68], + [63, 63, 63, 69, 66, 69, 66, 69, 69, 63, 63, 63, 63, 63, 63, 65, 65, 65, 65, 65, 65, 63], + [63, 63, 66, 66, 66, 69, 66, 66, 66, 66, 63, 27, 28, 50, 36, 36, 0, 16, 13, 65, 65, 63], + [64, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, 65, 65, 65, 65, 64, 64], + [66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64] + ] +} diff --git a/test/expected/vbml/layouts_absolute_over_relative_components.json b/test/expected/vbml/layouts_absolute_over_relative_components.json new file mode 100644 index 0000000..9265cd2 --- /dev/null +++ b/test/expected/vbml/layouts_absolute_over_relative_components.json @@ -0,0 +1,26 @@ +{ + "result": [ + [4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/layouts_absolute_over_relative_components_standard_size.json b/test/expected/vbml/layouts_absolute_over_relative_components_standard_size.json new file mode 100644 index 0000000..e4c9c84 --- /dev/null +++ b/test/expected/vbml/layouts_absolute_over_relative_components_standard_size.json @@ -0,0 +1,10 @@ +{ + "result": [ + [4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/layouts_calendar_component_for_christmas.json b/test/expected/vbml/layouts_calendar_component_for_christmas.json new file mode 100644 index 0000000..8194fb7 --- /dev/null +++ b/test/expected/vbml/layouts_calendar_component_for_christmas.json @@ -0,0 +1,10 @@ +{ + "result": [ + [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 33, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 34, 44, 27, 30, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 31, 44, 28, 27, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 28, 44, 28, 34, 66, 66, 66, 63, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 35, 44, 29, 27, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/layouts_calendar_component_on_the_right.json b/test/expected/vbml/layouts_calendar_component_on_the_right.json new file mode 100644 index 0000000..8fe843e --- /dev/null +++ b/test/expected/vbml/layouts_calendar_component_on_the_right.json @@ -0,0 +1,10 @@ +{ + "result": [ + [13, 5, 18, 18, 25, 0, 0, 0, 0, 0, 27, 28, 59, 28, 34, 19, 13, 20, 23, 20, 6, 19], + [3, 8, 18, 9, 19, 20, 13, 1, 19, 0, 0, 27, 44, 28, 0, 0, 0, 0, 0, 0, 66, 66], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 44, 35, 0, 66, 66, 66, 66, 66, 66, 66], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 36, 44, 27, 32, 66, 66, 66, 66, 66, 66, 66], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 33, 44, 28, 29, 66, 66, 66, 66, 66, 66, 66], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 30, 44, 29, 27, 66, 63, 66, 66, 66, 66, 66] + ] +} diff --git a/test/expected/vbml/layouts_calendar_component_with_other_components.json b/test/expected/vbml/layouts_calendar_component_with_other_components.json new file mode 100644 index 0000000..9ce4033 --- /dev/null +++ b/test/expected/vbml/layouts_calendar_component_with_other_components.json @@ -0,0 +1,10 @@ +{ + "result": [ + [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 4, 5, 3, 5, 13, 2, 5, 18, 0], + [0, 27, 44, 33, 0, 63, 64, 65, 66, 67, 68, 63, 0, 28, 36, 28, 30, 0, 0, 0, 0, 0], + [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 3, 1, 12, 5, 14, 4, 1, 18, 0], + [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 35, 44, 29, 27, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/layouts_components_side_by_side.json b/test/expected/vbml/layouts_components_side_by_side.json new file mode 100644 index 0000000..4b858cc --- /dev/null +++ b/test/expected/vbml/layouts_components_side_by_side.json @@ -0,0 +1,5 @@ +{ + "result": [ + [8, 9, 8, 9] + ] +} diff --git a/test/expected/vbml/layouts_components_vertically.json b/test/expected/vbml/layouts_components_vertically.json new file mode 100644 index 0000000..07f0166 --- /dev/null +++ b/test/expected/vbml/layouts_components_vertically.json @@ -0,0 +1,6 @@ +{ + "result": [ + [8, 9], + [8, 9] + ] +} diff --git a/test/expected/vbml/layouts_minimalist_calendar_component.json b/test/expected/vbml/layouts_minimalist_calendar_component.json new file mode 100644 index 0000000..4094659 --- /dev/null +++ b/test/expected/vbml/layouts_minimalist_calendar_component.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 66, 66, 66, 63, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/layouts_raw_components.json b/test/expected/vbml/layouts_raw_components.json new file mode 100644 index 0000000..31735a7 --- /dev/null +++ b/test/expected/vbml/layouts_raw_components.json @@ -0,0 +1,10 @@ +{ + "result": [ + [1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/parses_single_component_on_a_board.json b/test/expected/vbml/parses_single_component_on_a_board.json new file mode 100644 index 0000000..f249c53 --- /dev/null +++ b/test/expected/vbml/parses_single_component_on_a_board.json @@ -0,0 +1,5 @@ +{ + "result": [ + [8, 9] + ] +} diff --git a/test/expected/vbml/respects_double_returns.json b/test/expected/vbml/respects_double_returns.json new file mode 100644 index 0000000..c3c4027 --- /dev/null +++ b/test/expected/vbml/respects_double_returns.json @@ -0,0 +1,7 @@ +{ + "result": [ + [8, 0], + [0, 0], + [9, 0] + ] +} diff --git a/test/expected/vbml/respects_triple_returns.json b/test/expected/vbml/respects_triple_returns.json new file mode 100644 index 0000000..41070fa --- /dev/null +++ b/test/expected/vbml/respects_triple_returns.json @@ -0,0 +1,8 @@ +{ + "result": [ + [8, 0], + [0, 0], + [0, 0], + [9, 0] + ] +} diff --git a/test/input/calendar/creates_calendar_with_four_weeks.json b/test/input/calendar/creates_calendar_with_four_weeks.json new file mode 100644 index 0000000..df32833 --- /dev/null +++ b/test/input/calendar/creates_calendar_with_four_weeks.json @@ -0,0 +1,5 @@ +{ + "month": "2", + "year": "2026", + "days": {} +} diff --git a/test/input/calendar/does_not_highlight_days_outside_of_month.json b/test/input/calendar/does_not_highlight_days_outside_of_month.json new file mode 100644 index 0000000..55cc07b --- /dev/null +++ b/test/input/calendar/does_not_highlight_days_outside_of_month.json @@ -0,0 +1,9 @@ +{ + "month": "6", + "year": "2026", + "days": { + "30": 67, + "31": 67, + "32": 67 + } +} diff --git a/test/input/calendar/renders_single_date_header_when_last_week_has_one_day.json b/test/input/calendar/renders_single_date_header_when_last_week_has_one_day.json new file mode 100644 index 0000000..bfd94b7 --- /dev/null +++ b/test/input/calendar/renders_single_date_header_when_last_week_has_one_day.json @@ -0,0 +1,5 @@ +{ + "month": "2", + "year": "2027", + "days": {} +} diff --git a/test/input/characterCodesToAscii/converts_color_codes.json b/test/input/characterCodesToAscii/converts_color_codes.json new file mode 100644 index 0000000..859e9da --- /dev/null +++ b/test/input/characterCodesToAscii/converts_color_codes.json @@ -0,0 +1,14 @@ +{ + "characterCodes": [ + [ + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70 + ] + ] +} diff --git a/test/input/characterCodesToAscii/handles_multiple_rows.json b/test/input/characterCodesToAscii/handles_multiple_rows.json new file mode 100644 index 0000000..f90f73f --- /dev/null +++ b/test/input/characterCodesToAscii/handles_multiple_rows.json @@ -0,0 +1,12 @@ +{ + "characterCodes": [ + [ + 63, + 64 + ], + [ + 63, + 64 + ] + ] +} diff --git a/test/input/characterCodesToAscii/spaces_out_letters.json b/test/input/characterCodesToAscii/spaces_out_letters.json new file mode 100644 index 0000000..4dc9fc5 --- /dev/null +++ b/test/input/characterCodesToAscii/spaces_out_letters.json @@ -0,0 +1,8 @@ +{ + "characterCodes": [ + [ + 1, + 2 + ] + ] +} diff --git a/test/input/characterCodesToString/converts_two_line_sentence.json b/test/input/characterCodesToString/converts_two_line_sentence.json new file mode 100644 index 0000000..b93abeb --- /dev/null +++ b/test/input/characterCodesToString/converts_two_line_sentence.json @@ -0,0 +1,148 @@ +{ + "characters": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 20, + 8, + 9, + 19, + 0, + 9, + 19, + 0, + 1, + 0, + 12, + 15, + 14, + 7, + 5, + 18, + 0, + 2, + 12, + 15, + 3, + 11 + ], + [ + 20, + 8, + 1, + 20, + 0, + 19, + 16, + 1, + 14, + 19, + 0, + 28, + 0, + 12, + 9, + 14, + 5, + 19, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] +} diff --git a/test/input/characterCodesToString/converts_word_to_string.json b/test/input/characterCodesToString/converts_word_to_string.json new file mode 100644 index 0000000..db32eb7 --- /dev/null +++ b/test/input/characterCodesToString/converts_word_to_string.json @@ -0,0 +1,8 @@ +{ + "characters": [ + [ + 1, + 2 + ] + ] +} diff --git a/test/input/characterCodesToString/does_not_insert_line_break_when_first_word_fits.json b/test/input/characterCodesToString/does_not_insert_line_break_when_first_word_fits.json new file mode 100644 index 0000000..0e36f87 --- /dev/null +++ b/test/input/characterCodesToString/does_not_insert_line_break_when_first_word_fits.json @@ -0,0 +1,15 @@ +{ + "characters": [ + [ + 1, + 0 + ], + [ + 2, + 0 + ] + ], + "options": { + "allowLineBreaks": true + } +} diff --git a/test/input/characterCodesToString/handles_breaks.json b/test/input/characterCodesToString/handles_breaks.json new file mode 100644 index 0000000..baab393 --- /dev/null +++ b/test/input/characterCodesToString/handles_breaks.json @@ -0,0 +1,52 @@ +{ + "characters": [ + [ + 0, + 0, + 8, + 1, + 14, + 4, + 12, + 5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 2, + 18, + 5, + 1, + 11, + 19, + 0, + 7, + 18, + 1, + 3, + 5, + 6, + 21, + 12, + 12, + 25, + 0, + 0, + 0 + ] + ] +} diff --git a/test/input/characterCodesToString/handles_line_breaks.json b/test/input/characterCodesToString/handles_line_breaks.json new file mode 100644 index 0000000..8387d1e --- /dev/null +++ b/test/input/characterCodesToString/handles_line_breaks.json @@ -0,0 +1,21 @@ +{ + "characters": [ + [ + 1, + 2, + 0, + 0, + 0 + ], + [ + 3, + 4, + 0, + 0, + 0 + ] + ], + "options": { + "allowLineBreaks": true + } +} diff --git a/test/input/classic/converts_ae_umlaut.json b/test/input/classic/converts_ae_umlaut.json new file mode 100644 index 0000000..113678a --- /dev/null +++ b/test/input/classic/converts_ae_umlaut.json @@ -0,0 +1,3 @@ +{ + "text": "ƤƄ" +} diff --git a/test/input/classic/converts_double_newline.json b/test/input/classic/converts_double_newline.json new file mode 100644 index 0000000..95561dc --- /dev/null +++ b/test/input/classic/converts_double_newline.json @@ -0,0 +1,3 @@ +{ + "text": "hello\n\nworld" +} diff --git a/test/input/classic/converts_embedded_char_code_string.json b/test/input/classic/converts_embedded_char_code_string.json new file mode 100644 index 0000000..5000b6a --- /dev/null +++ b/test/input/classic/converts_embedded_char_code_string.json @@ -0,0 +1,6 @@ +{ + "text": "Hello, World{37} 37 {65}66{65} 89{65} {65}20 hello world whatsup", + "options": { + "extraHPadding": 0 + } +} diff --git a/test/input/classic/converts_emoji_colors.json b/test/input/classic/converts_emoji_colors.json new file mode 100644 index 0000000..bed5894 --- /dev/null +++ b/test/input/classic/converts_emoji_colors.json @@ -0,0 +1,3 @@ +{ + "text": "šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›" +} diff --git a/test/input/classic/converts_hyphen_string.json b/test/input/classic/converts_hyphen_string.json new file mode 100644 index 0000000..b90288b --- /dev/null +++ b/test/input/classic/converts_hyphen_string.json @@ -0,0 +1,3 @@ +{ + "text": "- -hyphen" +} diff --git a/test/input/classic/converts_long_string_with_digits.json b/test/input/classic/converts_long_string_with_digits.json new file mode 100644 index 0000000..452edec --- /dev/null +++ b/test/input/classic/converts_long_string_with_digits.json @@ -0,0 +1,3 @@ +{ + "text": "reallylongwordthatismorethan22charcters" +} diff --git a/test/input/classic/converts_longer_string.json b/test/input/classic/converts_longer_string.json new file mode 100644 index 0000000..bdc5dad --- /dev/null +++ b/test/input/classic/converts_longer_string.json @@ -0,0 +1,3 @@ +{ + "text": "reallylongwordthatismorethantwentytwocharcters" +} diff --git a/test/input/classic/converts_single_char_code.json b/test/input/classic/converts_single_char_code.json new file mode 100644 index 0000000..231f8e1 --- /dev/null +++ b/test/input/classic/converts_single_char_code.json @@ -0,0 +1,3 @@ +{ + "text": "{1}" +} diff --git a/test/input/classic/converts_single_newline.json b/test/input/classic/converts_single_newline.json new file mode 100644 index 0000000..e3c5f3c --- /dev/null +++ b/test/input/classic/converts_single_newline.json @@ -0,0 +1,3 @@ +{ + "text": "hello\n world" +} diff --git a/test/input/classic/converts_special_characters.json b/test/input/classic/converts_special_characters.json new file mode 100644 index 0000000..26bd82e --- /dev/null +++ b/test/input/classic/converts_special_characters.json @@ -0,0 +1,3 @@ +{ + "text": "!@#$%^&*()_+Ć„ĆŸāˆ‚Ę’Ā©Ė™āˆ†ĖšĀ¬ĀµāˆšĆ§āˆ«ĖœĀµā‰¤ā‰„Ć·{}" +} diff --git a/test/input/classic/converts_string_to_classic_board.json b/test/input/classic/converts_string_to_classic_board.json new file mode 100644 index 0000000..392e097 --- /dev/null +++ b/test/input/classic/converts_string_to_classic_board.json @@ -0,0 +1,3 @@ +{ + "text": "Hello, World!" +} diff --git a/test/input/classic/preserves_triple_spaces.json b/test/input/classic/preserves_triple_spaces.json new file mode 100644 index 0000000..1ec6df8 --- /dev/null +++ b/test/input/classic/preserves_triple_spaces.json @@ -0,0 +1,6 @@ +{ + "text": "hello world", + "options": { + "preserveDoubleSpaces": true + } +} diff --git a/test/input/classic/respects_double_spaces.json b/test/input/classic/respects_double_spaces.json new file mode 100644 index 0000000..e5a7117 --- /dev/null +++ b/test/input/classic/respects_double_spaces.json @@ -0,0 +1,6 @@ +{ + "text": "hello world", + "options": { + "preserveDoubleSpaces": true + } +} diff --git a/test/input/classic/returns_empty_board_for_empty_string.json b/test/input/classic/returns_empty_board_for_empty_string.json new file mode 100644 index 0000000..63de83f --- /dev/null +++ b/test/input/classic/returns_empty_board_for_empty_string.json @@ -0,0 +1,3 @@ +{ + "text": "" +} diff --git a/test/input/hasSpecialCharacters/excludes_black_color_swatch.json b/test/input/hasSpecialCharacters/excludes_black_color_swatch.json new file mode 100644 index 0000000..302cc75 --- /dev/null +++ b/test/input/hasSpecialCharacters/excludes_black_color_swatch.json @@ -0,0 +1,3 @@ +{ + "text": "⬛" +} diff --git a/test/input/hasSpecialCharacters/excludes_ios_double_quote.json b/test/input/hasSpecialCharacters/excludes_ios_double_quote.json new file mode 100644 index 0000000..92d34c5 --- /dev/null +++ b/test/input/hasSpecialCharacters/excludes_ios_double_quote.json @@ -0,0 +1,3 @@ +{ + "text": "ā€œ" +} diff --git a/test/input/hasSpecialCharacters/excludes_ios_single_quote.json b/test/input/hasSpecialCharacters/excludes_ios_single_quote.json new file mode 100644 index 0000000..35008fb --- /dev/null +++ b/test/input/hasSpecialCharacters/excludes_ios_single_quote.json @@ -0,0 +1,3 @@ +{ + "text": "ā€˜" +} diff --git a/test/input/hasSpecialCharacters/excludes_newlines.json b/test/input/hasSpecialCharacters/excludes_newlines.json new file mode 100644 index 0000000..c91c455 --- /dev/null +++ b/test/input/hasSpecialCharacters/excludes_newlines.json @@ -0,0 +1,3 @@ +{ + "text": "Hello\nWorld" +} diff --git a/test/input/hasSpecialCharacters/excludes_orange_color_swatch.json b/test/input/hasSpecialCharacters/excludes_orange_color_swatch.json new file mode 100644 index 0000000..8602d6a --- /dev/null +++ b/test/input/hasSpecialCharacters/excludes_orange_color_swatch.json @@ -0,0 +1,3 @@ +{ + "text": "🟧" +} diff --git a/test/input/hasSpecialCharacters/excludes_white_color_swatch.json b/test/input/hasSpecialCharacters/excludes_white_color_swatch.json new file mode 100644 index 0000000..4601a80 --- /dev/null +++ b/test/input/hasSpecialCharacters/excludes_white_color_swatch.json @@ -0,0 +1,3 @@ +{ + "text": "⬜" +} diff --git a/test/input/hasSpecialCharacters/includes_fractions.json b/test/input/hasSpecialCharacters/includes_fractions.json new file mode 100644 index 0000000..28d27a4 --- /dev/null +++ b/test/input/hasSpecialCharacters/includes_fractions.json @@ -0,0 +1,3 @@ +{ + "text": "½" +} diff --git a/test/input/hasSpecialCharacters/returns_false_for_empty_string.json b/test/input/hasSpecialCharacters/returns_false_for_empty_string.json new file mode 100644 index 0000000..63de83f --- /dev/null +++ b/test/input/hasSpecialCharacters/returns_false_for_empty_string.json @@ -0,0 +1,3 @@ +{ + "text": "" +} diff --git a/test/input/hasSpecialCharacters/returns_false_for_lowercase_alphabet.json b/test/input/hasSpecialCharacters/returns_false_for_lowercase_alphabet.json new file mode 100644 index 0000000..a0373ac --- /dev/null +++ b/test/input/hasSpecialCharacters/returns_false_for_lowercase_alphabet.json @@ -0,0 +1,3 @@ +{ + "text": "abcdefghijklmnopqrstuvwxyz" +} diff --git a/test/input/hasSpecialCharacters/returns_false_for_numbers.json b/test/input/hasSpecialCharacters/returns_false_for_numbers.json new file mode 100644 index 0000000..e9fd533 --- /dev/null +++ b/test/input/hasSpecialCharacters/returns_false_for_numbers.json @@ -0,0 +1,3 @@ +{ + "text": "0123456789" +} diff --git a/test/input/hasSpecialCharacters/returns_false_for_supported_symbols.json b/test/input/hasSpecialCharacters/returns_false_for_supported_symbols.json new file mode 100644 index 0000000..1fc6712 --- /dev/null +++ b/test/input/hasSpecialCharacters/returns_false_for_supported_symbols.json @@ -0,0 +1,3 @@ +{ + "text": "!@#$()-+&=;:'\"%,./?°" +} diff --git a/test/input/hasSpecialCharacters/returns_false_for_uppercase_alphabet.json b/test/input/hasSpecialCharacters/returns_false_for_uppercase_alphabet.json new file mode 100644 index 0000000..ced4f1e --- /dev/null +++ b/test/input/hasSpecialCharacters/returns_false_for_uppercase_alphabet.json @@ -0,0 +1,3 @@ +{ + "text": "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +} diff --git a/test/input/hasSpecialCharacters/returns_true_for_mixed_special_and_standard_characters.json b/test/input/hasSpecialCharacters/returns_true_for_mixed_special_and_standard_characters.json new file mode 100644 index 0000000..b2a5b7b --- /dev/null +++ b/test/input/hasSpecialCharacters/returns_true_for_mixed_special_and_standard_characters.json @@ -0,0 +1,3 @@ +{ + "text": "Ƥa" +} diff --git a/test/input/hasSpecialCharacters/returns_true_for_special_character.json b/test/input/hasSpecialCharacters/returns_true_for_special_character.json new file mode 100644 index 0000000..490e15d --- /dev/null +++ b/test/input/hasSpecialCharacters/returns_true_for_special_character.json @@ -0,0 +1,3 @@ +{ + "text": "Ƥ" +} diff --git a/test/input/parseComponent/adds_extra_spaces.json b/test/input/parseComponent/adds_extra_spaces.json new file mode 100644 index 0000000..a1898ea --- /dev/null +++ b/test/input/parseComponent/adds_extra_spaces.json @@ -0,0 +1,7 @@ +{ + "height": 1, + "width": 13, + "component": { + "template": "Hello World!" + } +} diff --git a/test/input/parseComponent/adds_template_props.json b/test/input/parseComponent/adds_template_props.json new file mode 100644 index 0000000..5dcd3e1 --- /dev/null +++ b/test/input/parseComponent/adds_template_props.json @@ -0,0 +1,10 @@ +{ + "height": 1, + "width": 11, + "props": { + "greeting": "Hello" + }, + "component": { + "template": "{{greeting}} World" + } +} diff --git a/test/input/parseComponent/allows_array_iteration.json b/test/input/parseComponent/allows_array_iteration.json new file mode 100644 index 0000000..0d5296b --- /dev/null +++ b/test/input/parseComponent/allows_array_iteration.json @@ -0,0 +1,14 @@ +{ + "height": 1, + "width": 3, + "props": { + "numbers": [ + 1, + 2, + 3 + ] + }, + "component": { + "template": "{{#numbers}}{{.}}{{/numbers}}" + } +} diff --git a/test/input/parseComponent/allows_conditions_when_false.json b/test/input/parseComponent/allows_conditions_when_false.json new file mode 100644 index 0000000..f2b6bb6 --- /dev/null +++ b/test/input/parseComponent/allows_conditions_when_false.json @@ -0,0 +1,10 @@ +{ + "height": 1, + "width": 10, + "props": { + "isHappy": false + }, + "component": { + "template": "I am {{#isHappy}}Happy{{/isHappy}}{{^isHappy}}Mad{{/isHappy}}" + } +} diff --git a/test/input/parseComponent/allows_conditions_when_true.json b/test/input/parseComponent/allows_conditions_when_true.json new file mode 100644 index 0000000..b67f181 --- /dev/null +++ b/test/input/parseComponent/allows_conditions_when_true.json @@ -0,0 +1,10 @@ +{ + "height": 1, + "width": 10, + "props": { + "isHappy": true + }, + "component": { + "template": "I am {{#isHappy}}Happy{{/isHappy}}{{^isHappy}}Mad{{/isHappy}}" + } +} diff --git a/test/input/parseComponent/allows_newlines.json b/test/input/parseComponent/allows_newlines.json new file mode 100644 index 0000000..69f5db4 --- /dev/null +++ b/test/input/parseComponent/allows_newlines.json @@ -0,0 +1,7 @@ +{ + "height": 2, + "width": 2, + "component": { + "template": "{1}\n{1}" + } +} diff --git a/test/input/parseComponent/allows_newlines_after_spaces.json b/test/input/parseComponent/allows_newlines_after_spaces.json new file mode 100644 index 0000000..b9d0959 --- /dev/null +++ b/test/input/parseComponent/allows_newlines_after_spaces.json @@ -0,0 +1,7 @@ +{ + "height": 2, + "width": 2, + "component": { + "template": "{1} \n{1}" + } +} diff --git a/test/input/parseComponent/allows_newlines_before_spaces.json b/test/input/parseComponent/allows_newlines_before_spaces.json new file mode 100644 index 0000000..ff76411 --- /dev/null +++ b/test/input/parseComponent/allows_newlines_before_spaces.json @@ -0,0 +1,10 @@ +{ + "height": 2, + "width": 2, + "component": { + "template": "{1}\n{70}{1}", + "style": { + "justify": "center" + } + } +} diff --git a/test/input/parseComponent/automatically_breaks_line.json b/test/input/parseComponent/automatically_breaks_line.json new file mode 100644 index 0000000..825279d --- /dev/null +++ b/test/input/parseComponent/automatically_breaks_line.json @@ -0,0 +1,7 @@ +{ + "height": 2, + "width": 6, + "component": { + "template": "Hello World!" + } +} diff --git a/test/input/parseComponent/breaks_on_lines_with_character_codes.json b/test/input/parseComponent/breaks_on_lines_with_character_codes.json new file mode 100644 index 0000000..ce318d6 --- /dev/null +++ b/test/input/parseComponent/breaks_on_lines_with_character_codes.json @@ -0,0 +1,11 @@ +{ + "height": 2, + "width": 3, + "component": { + "template": "{1}{2} {3}{4}", + "style": { + "justify": "center", + "align": "center" + } + } +} diff --git a/test/input/parseComponent/converts_emoji_characters_to_character_codes.json b/test/input/parseComponent/converts_emoji_characters_to_character_codes.json new file mode 100644 index 0000000..106bc8a --- /dev/null +++ b/test/input/parseComponent/converts_emoji_characters_to_character_codes.json @@ -0,0 +1,7 @@ +{ + "height": 1, + "width": 8, + "component": { + "template": "šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›" + } +} diff --git a/test/input/parseComponent/does_not_break_when_unnecessary.json b/test/input/parseComponent/does_not_break_when_unnecessary.json new file mode 100644 index 0000000..5eb67cf --- /dev/null +++ b/test/input/parseComponent/does_not_break_when_unnecessary.json @@ -0,0 +1,7 @@ +{ + "height": 3, + "width": 13, + "component": { + "template": "Hello World!" + } +} diff --git a/test/input/parseComponent/formats_longer_message_centered.json b/test/input/parseComponent/formats_longer_message_centered.json new file mode 100644 index 0000000..1c0472d --- /dev/null +++ b/test/input/parseComponent/formats_longer_message_centered.json @@ -0,0 +1,10 @@ +{ + "height": 2, + "width": 22, + "component": { + "template": "Thank you for having us!", + "style": { + "justify": "center" + } + } +} diff --git a/test/input/parseComponent/formats_longer_plain_text.json b/test/input/parseComponent/formats_longer_plain_text.json new file mode 100644 index 0000000..eaf3b86 --- /dev/null +++ b/test/input/parseComponent/formats_longer_plain_text.json @@ -0,0 +1,7 @@ +{ + "height": 2, + "width": 12, + "component": { + "template": "Thank you for having us!" + } +} diff --git a/test/input/parseComponent/formats_plain_text.json b/test/input/parseComponent/formats_plain_text.json new file mode 100644 index 0000000..e74a479 --- /dev/null +++ b/test/input/parseComponent/formats_plain_text.json @@ -0,0 +1,7 @@ +{ + "height": 1, + "width": 12, + "component": { + "template": "Hello World!" + } +} diff --git a/test/input/parseComponent/horizontally_aligns_center.json b/test/input/parseComponent/horizontally_aligns_center.json new file mode 100644 index 0000000..93c5643 --- /dev/null +++ b/test/input/parseComponent/horizontally_aligns_center.json @@ -0,0 +1,10 @@ +{ + "height": 1, + "width": 3, + "component": { + "template": "!", + "style": { + "justify": "center" + } + } +} diff --git a/test/input/parseComponent/horizontally_aligns_justified.json b/test/input/parseComponent/horizontally_aligns_justified.json new file mode 100644 index 0000000..390a602 --- /dev/null +++ b/test/input/parseComponent/horizontally_aligns_justified.json @@ -0,0 +1,11 @@ +{ + "height": 6, + "width": 22, + "component": { + "template": "Testing Testing 123", + "style": { + "align": "center", + "justify": "justified" + } + } +} diff --git a/test/input/parseComponent/horizontally_aligns_right.json b/test/input/parseComponent/horizontally_aligns_right.json new file mode 100644 index 0000000..d50f401 --- /dev/null +++ b/test/input/parseComponent/horizontally_aligns_right.json @@ -0,0 +1,10 @@ +{ + "height": 1, + "width": 3, + "component": { + "template": "!", + "style": { + "justify": "right" + } + } +} diff --git a/test/input/parseComponent/horizontally_and_vertically_aligned_center.json b/test/input/parseComponent/horizontally_and_vertically_aligned_center.json new file mode 100644 index 0000000..82c8133 --- /dev/null +++ b/test/input/parseComponent/horizontally_and_vertically_aligned_center.json @@ -0,0 +1,11 @@ +{ + "height": 3, + "width": 3, + "component": { + "template": "!", + "style": { + "justify": "center", + "align": "center" + } + } +} diff --git a/test/input/parseComponent/justified_long_complex_message.json b/test/input/parseComponent/justified_long_complex_message.json new file mode 100644 index 0000000..cba15c4 --- /dev/null +++ b/test/input/parseComponent/justified_long_complex_message.json @@ -0,0 +1,11 @@ +{ + "height": 6, + "width": 22, + "component": { + "template": "Pack my box with five dozen liquor jugs. The quick brown fox jumps over the lazy dog. How vexingly quick daft zebras jump!", + "style": { + "align": "center", + "justify": "justified" + } + } +} diff --git a/test/input/parseComponent/justified_when_flowing_to_next_line.json b/test/input/parseComponent/justified_when_flowing_to_next_line.json new file mode 100644 index 0000000..9bf9996 --- /dev/null +++ b/test/input/parseComponent/justified_when_flowing_to_next_line.json @@ -0,0 +1,11 @@ +{ + "height": 6, + "width": 22, + "component": { + "template": "Testing Testing 123456789", + "style": { + "align": "center", + "justify": "justified" + } + } +} diff --git a/test/input/parseComponent/justified_when_full_line_covered.json b/test/input/parseComponent/justified_when_full_line_covered.json new file mode 100644 index 0000000..4c79b05 --- /dev/null +++ b/test/input/parseComponent/justified_when_full_line_covered.json @@ -0,0 +1,11 @@ +{ + "height": 6, + "width": 22, + "component": { + "template": "Testing Testing 123456", + "style": { + "align": "center", + "justify": "justified" + } + } +} diff --git a/test/input/parseComponent/parses_absolute_component.json b/test/input/parseComponent/parses_absolute_component.json new file mode 100644 index 0000000..5c57505 --- /dev/null +++ b/test/input/parseComponent/parses_absolute_component.json @@ -0,0 +1,16 @@ +{ + "mode": "absolute", + "height": 3, + "width": 12, + "component": { + "template": "Hello World!", + "style": { + "absolutePosition": { + "x": 4, + "y": 2 + }, + "width": 6, + "height": 2 + } + } +} diff --git a/test/input/parseComponent/parses_character_codes.json b/test/input/parseComponent/parses_character_codes.json new file mode 100644 index 0000000..0a88429 --- /dev/null +++ b/test/input/parseComponent/parses_character_codes.json @@ -0,0 +1,11 @@ +{ + "height": 1, + "width": 3, + "component": { + "template": "{1}{2}{3}", + "style": { + "justify": "center", + "align": "center" + } + } +} diff --git a/test/input/parseComponent/parses_raw_component.json b/test/input/parseComponent/parses_raw_component.json new file mode 100644 index 0000000..5e8adf3 --- /dev/null +++ b/test/input/parseComponent/parses_raw_component.json @@ -0,0 +1,16 @@ +{ + "height": 3, + "width": 12, + "component": { + "rawCharacters": [ + [ + 1, + 2 + ], + [ + 3, + 4 + ] + ] + } +} diff --git a/test/input/parseComponent/parses_two_digit_character_codes.json b/test/input/parseComponent/parses_two_digit_character_codes.json new file mode 100644 index 0000000..aa1e01f --- /dev/null +++ b/test/input/parseComponent/parses_two_digit_character_codes.json @@ -0,0 +1,11 @@ +{ + "height": 1, + "width": 2, + "component": { + "template": "{68}{69}", + "style": { + "justify": "center", + "align": "center" + } + } +} diff --git a/test/input/parseComponent/splits_long_words.json b/test/input/parseComponent/splits_long_words.json new file mode 100644 index 0000000..b64befb --- /dev/null +++ b/test/input/parseComponent/splits_long_words.json @@ -0,0 +1,7 @@ +{ + "height": 2, + "width": 2, + "component": { + "template": "{1}{2}{3}{4}" + } +} diff --git a/test/input/parseComponent/throws_for_invalid_character_codes.json b/test/input/parseComponent/throws_for_invalid_character_codes.json new file mode 100644 index 0000000..cdea609 --- /dev/null +++ b/test/input/parseComponent/throws_for_invalid_character_codes.json @@ -0,0 +1,11 @@ +{ + "height": 1, + "width": 1, + "component": { + "template": "{99}", + "style": { + "justify": "center", + "align": "center" + } + } +} diff --git a/test/input/parseComponent/vertically_aligns_bottom.json b/test/input/parseComponent/vertically_aligns_bottom.json new file mode 100644 index 0000000..51e4cb2 --- /dev/null +++ b/test/input/parseComponent/vertically_aligns_bottom.json @@ -0,0 +1,10 @@ +{ + "height": 4, + "width": 1, + "component": { + "template": "!", + "style": { + "align": "bottom" + } + } +} diff --git a/test/input/parseComponent/vertically_aligns_center.json b/test/input/parseComponent/vertically_aligns_center.json new file mode 100644 index 0000000..0f0e45b --- /dev/null +++ b/test/input/parseComponent/vertically_aligns_center.json @@ -0,0 +1,10 @@ +{ + "height": 3, + "width": 1, + "component": { + "template": "!", + "style": { + "align": "center" + } + } +} diff --git a/test/input/parseComponent/vertically_aligns_center_multiple_rows.json b/test/input/parseComponent/vertically_aligns_center_multiple_rows.json new file mode 100644 index 0000000..22d71f7 --- /dev/null +++ b/test/input/parseComponent/vertically_aligns_center_multiple_rows.json @@ -0,0 +1,10 @@ +{ + "height": 5, + "width": 1, + "component": { + "template": "!", + "style": { + "align": "center" + } + } +} diff --git a/test/input/parseComponent/vertically_aligns_center_sticks_to_top_when_no_even_padding.json b/test/input/parseComponent/vertically_aligns_center_sticks_to_top_when_no_even_padding.json new file mode 100644 index 0000000..a102603 --- /dev/null +++ b/test/input/parseComponent/vertically_aligns_center_sticks_to_top_when_no_even_padding.json @@ -0,0 +1,10 @@ +{ + "height": 6, + "width": 1, + "component": { + "template": "!", + "style": { + "align": "center" + } + } +} diff --git a/test/input/sanitizeSpecialCharacters/accepts_whitespace_after_heart.json b/test/input/sanitizeSpecialCharacters/accepts_whitespace_after_heart.json new file mode 100644 index 0000000..3086fc0 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/accepts_whitespace_after_heart.json @@ -0,0 +1,3 @@ +{ + "text": "ā¤ " +} diff --git a/test/input/sanitizeSpecialCharacters/converts_cedilla.json b/test/input/sanitizeSpecialCharacters/converts_cedilla.json new file mode 100644 index 0000000..ddcc6ff --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_cedilla.json @@ -0,0 +1,3 @@ +{ + "text": "Ƨ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_ellipsis.json b/test/input/sanitizeSpecialCharacters/converts_ellipsis.json new file mode 100644 index 0000000..3541e43 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_ellipsis.json @@ -0,0 +1,3 @@ +{ + "text": "…" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_en_dash.json b/test/input/sanitizeSpecialCharacters/converts_en_dash.json new file mode 100644 index 0000000..fbd3c94 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_en_dash.json @@ -0,0 +1,3 @@ +{ + "text": "–" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_f_hook.json b/test/input/sanitizeSpecialCharacters/converts_f_hook.json new file mode 100644 index 0000000..1ec1d0e --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_f_hook.json @@ -0,0 +1,3 @@ +{ + "text": "ʒ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_fraction_slash.json b/test/input/sanitizeSpecialCharacters/converts_fraction_slash.json new file mode 100644 index 0000000..c9b2025 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_fraction_slash.json @@ -0,0 +1,3 @@ +{ + "text": "⁄" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_german_lowercase_ae.json b/test/input/sanitizeSpecialCharacters/converts_german_lowercase_ae.json new file mode 100644 index 0000000..490e15d --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_german_lowercase_ae.json @@ -0,0 +1,3 @@ +{ + "text": "Ƥ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_german_lowercase_oe.json b/test/input/sanitizeSpecialCharacters/converts_german_lowercase_oe.json new file mode 100644 index 0000000..caea242 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_german_lowercase_oe.json @@ -0,0 +1,3 @@ +{ + "text": "ƶ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_german_lowercase_ue.json b/test/input/sanitizeSpecialCharacters/converts_german_lowercase_ue.json new file mode 100644 index 0000000..9decfbb --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_german_lowercase_ue.json @@ -0,0 +1,3 @@ +{ + "text": "ü" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_german_uppercase_ae.json b/test/input/sanitizeSpecialCharacters/converts_german_uppercase_ae.json new file mode 100644 index 0000000..937299f --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_german_uppercase_ae.json @@ -0,0 +1,3 @@ +{ + "text": "Ƅ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_german_uppercase_oe.json b/test/input/sanitizeSpecialCharacters/converts_german_uppercase_oe.json new file mode 100644 index 0000000..42d1ceb --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_german_uppercase_oe.json @@ -0,0 +1,3 @@ +{ + "text": "Ɩ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_german_uppercase_ue.json b/test/input/sanitizeSpecialCharacters/converts_german_uppercase_ue.json new file mode 100644 index 0000000..7c96ec4 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_german_uppercase_ue.json @@ -0,0 +1,3 @@ +{ + "text": "Ü" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_ligature_ae.json b/test/input/sanitizeSpecialCharacters/converts_ligature_ae.json new file mode 100644 index 0000000..3c69a75 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_ligature_ae.json @@ -0,0 +1,3 @@ +{ + "text": "Ʀ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_ligature_oe.json b/test/input/sanitizeSpecialCharacters/converts_ligature_oe.json new file mode 100644 index 0000000..a28815e --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_ligature_oe.json @@ -0,0 +1,3 @@ +{ + "text": "œ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_micro_sign_to_space.json b/test/input/sanitizeSpecialCharacters/converts_micro_sign_to_space.json new file mode 100644 index 0000000..651d6d3 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_micro_sign_to_space.json @@ -0,0 +1,3 @@ +{ + "text": "µ" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_scandinavian_a_ring.json b/test/input/sanitizeSpecialCharacters/converts_scandinavian_a_ring.json new file mode 100644 index 0000000..1059e8d --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_scandinavian_a_ring.json @@ -0,0 +1,3 @@ +{ + "text": "Ć„" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_scandinavian_o_stroke.json b/test/input/sanitizeSpecialCharacters/converts_scandinavian_o_stroke.json new file mode 100644 index 0000000..ed6a32d --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_scandinavian_o_stroke.json @@ -0,0 +1,3 @@ +{ + "text": "Ćø" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_sharp_s.json b/test/input/sanitizeSpecialCharacters/converts_sharp_s.json new file mode 100644 index 0000000..bd5a399 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_sharp_s.json @@ -0,0 +1,3 @@ +{ + "text": "ß" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_fussball.json b/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_fussball.json new file mode 100644 index 0000000..2f03a9c --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_fussball.json @@ -0,0 +1,3 @@ +{ + "text": "fußball" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_gross.json b/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_gross.json new file mode 100644 index 0000000..cf66125 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_gross.json @@ -0,0 +1,3 @@ +{ + "text": "groß" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_weiss.json b/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_weiss.json new file mode 100644 index 0000000..6c6ea58 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_sharp_s_in_weiss.json @@ -0,0 +1,3 @@ +{ + "text": "weiß" +} diff --git a/test/input/sanitizeSpecialCharacters/converts_unsupported_sequenced_emojis_to_whitespace.json b/test/input/sanitizeSpecialCharacters/converts_unsupported_sequenced_emojis_to_whitespace.json new file mode 100644 index 0000000..b656adb --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/converts_unsupported_sequenced_emojis_to_whitespace.json @@ -0,0 +1,3 @@ +{ + "text": "ā˜ ļøāš ļøāœ…ā–¶ļøāœØāŒ›ļø" +} diff --git a/test/input/sanitizeSpecialCharacters/does_not_modify_plain_text.json b/test/input/sanitizeSpecialCharacters/does_not_modify_plain_text.json new file mode 100644 index 0000000..a0373ac --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/does_not_modify_plain_text.json @@ -0,0 +1,3 @@ +{ + "text": "abcdefghijklmnopqrstuvwxyz" +} diff --git a/test/input/sanitizeSpecialCharacters/does_not_replace_vestaboard_heart.json b/test/input/sanitizeSpecialCharacters/does_not_replace_vestaboard_heart.json new file mode 100644 index 0000000..f4c6c32 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/does_not_replace_vestaboard_heart.json @@ -0,0 +1,3 @@ +{ + "text": "ā¤" +} diff --git a/test/input/sanitizeSpecialCharacters/handles_german_sharp_s_in_context.json b/test/input/sanitizeSpecialCharacters/handles_german_sharp_s_in_context.json new file mode 100644 index 0000000..a48ea02 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/handles_german_sharp_s_in_context.json @@ -0,0 +1,3 @@ +{ + "text": "Straße" +} diff --git a/test/input/sanitizeSpecialCharacters/handles_german_text_with_umlauts_in_context.json b/test/input/sanitizeSpecialCharacters/handles_german_text_with_umlauts_in_context.json new file mode 100644 index 0000000..c57c3b5 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/handles_german_text_with_umlauts_in_context.json @@ -0,0 +1,3 @@ +{ + "text": "Über die Brücke gehen wir für Ɩsterreich" +} diff --git a/test/input/sanitizeSpecialCharacters/handles_heart_emoji_and_unsupported_emojis.json b/test/input/sanitizeSpecialCharacters/handles_heart_emoji_and_unsupported_emojis.json new file mode 100644 index 0000000..6dc87d5 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/handles_heart_emoji_and_unsupported_emojis.json @@ -0,0 +1,3 @@ +{ + "text": "ā¤ļøā˜ ļøāš ļøāœ…ā–¶ļøāœØāŒ›ļø" +} diff --git a/test/input/sanitizeSpecialCharacters/handles_mixed_special_characters.json b/test/input/sanitizeSpecialCharacters/handles_mixed_special_characters.json new file mode 100644 index 0000000..92ccc34 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/handles_mixed_special_characters.json @@ -0,0 +1,3 @@ +{ + "text": "hĆ©llo wĆ“rld" +} diff --git a/test/input/sanitizeSpecialCharacters/handles_multiple_special_characters_together.json b/test/input/sanitizeSpecialCharacters/handles_multiple_special_characters_together.json new file mode 100644 index 0000000..8ea0388 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/handles_multiple_special_characters_together.json @@ -0,0 +1,3 @@ +{ + "text": "ëï" +} diff --git a/test/input/sanitizeSpecialCharacters/handles_sentence.json b/test/input/sanitizeSpecialCharacters/handles_sentence.json new file mode 100644 index 0000000..da7d1ad --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/handles_sentence.json @@ -0,0 +1,3 @@ +{ + "text": "hello world" +} diff --git a/test/input/sanitizeSpecialCharacters/preserves_space_between_heart_and_emoji.json b/test/input/sanitizeSpecialCharacters/preserves_space_between_heart_and_emoji.json new file mode 100644 index 0000000..2fe011e --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/preserves_space_between_heart_and_emoji.json @@ -0,0 +1,3 @@ +{ + "text": "ā¤ 🟧" +} diff --git a/test/input/sanitizeSpecialCharacters/preserves_space_between_heart_and_latin_glyph.json b/test/input/sanitizeSpecialCharacters/preserves_space_between_heart_and_latin_glyph.json new file mode 100644 index 0000000..f645252 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/preserves_space_between_heart_and_latin_glyph.json @@ -0,0 +1,3 @@ +{ + "text": "ā¤ A" +} diff --git a/test/input/sanitizeSpecialCharacters/preserves_spaces_between_hearts.json b/test/input/sanitizeSpecialCharacters/preserves_spaces_between_hearts.json new file mode 100644 index 0000000..266c618 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/preserves_spaces_between_hearts.json @@ -0,0 +1,3 @@ +{ + "text": "ā¤ ā¤ ā¤ ā¤ ā¤" +} diff --git a/test/input/sanitizeSpecialCharacters/replaces_accented_character.json b/test/input/sanitizeSpecialCharacters/replaces_accented_character.json new file mode 100644 index 0000000..bbb5469 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/replaces_accented_character.json @@ -0,0 +1,3 @@ +{ + "text": "ƃ" +} diff --git a/test/input/sanitizeSpecialCharacters/replaces_fractions_with_multiple_characters.json b/test/input/sanitizeSpecialCharacters/replaces_fractions_with_multiple_characters.json new file mode 100644 index 0000000..28d27a4 --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/replaces_fractions_with_multiple_characters.json @@ -0,0 +1,3 @@ +{ + "text": "½" +} diff --git a/test/input/sanitizeSpecialCharacters/sanitizes_variation_selector_from_heart_emoji.json b/test/input/sanitizeSpecialCharacters/sanitizes_variation_selector_from_heart_emoji.json new file mode 100644 index 0000000..80b6c7a --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/sanitizes_variation_selector_from_heart_emoji.json @@ -0,0 +1,3 @@ +{ + "text": "ā¤ļø" +} diff --git a/test/input/sanitizeSpecialCharacters/sanitizes_variation_selector_from_unicode_literal.json b/test/input/sanitizeSpecialCharacters/sanitizes_variation_selector_from_unicode_literal.json new file mode 100644 index 0000000..80b6c7a --- /dev/null +++ b/test/input/sanitizeSpecialCharacters/sanitizes_variation_selector_from_unicode_literal.json @@ -0,0 +1,3 @@ +{ + "text": "ā¤ļø" +} diff --git a/test/input/vbml/flows_third_component_to_next_line.json b/test/input/vbml/flows_third_component_to_next_line.json new file mode 100644 index 0000000..a18b268 --- /dev/null +++ b/test/input/vbml/flows_third_component_to_next_line.json @@ -0,0 +1,29 @@ +{ + "style": { + "height": 2, + "width": 4 + }, + "components": [ + { + "template": "{1}{2}", + "style": { + "width": 2, + "height": 1 + } + }, + { + "template": "{3}{4}", + "style": { + "width": 2, + "height": 1 + } + }, + { + "template": "{5}{6}", + "style": { + "width": 2, + "height": 1 + } + } + ] +} diff --git a/test/input/vbml/formats_ae_umlaut.json b/test/input/vbml/formats_ae_umlaut.json new file mode 100644 index 0000000..dc7a2b4 --- /dev/null +++ b/test/input/vbml/formats_ae_umlaut.json @@ -0,0 +1,15 @@ +{ + "style": { + "height": 1, + "width": 4 + }, + "components": [ + { + "template": "ƤƄ", + "style": { + "width": 4, + "height": 1 + } + } + ] +} diff --git a/test/input/vbml/justifies_content_vertically.json b/test/input/vbml/justifies_content_vertically.json new file mode 100644 index 0000000..f160f96 --- /dev/null +++ b/test/input/vbml/justifies_content_vertically.json @@ -0,0 +1,16 @@ +{ + "style": { + "height": 5, + "width": 1 + }, + "components": [ + { + "template": "abcd", + "style": { + "height": 5, + "width": 1, + "align": "justified" + } + } + ] +} diff --git a/test/input/vbml/justifies_content_vertically_three_chars.json b/test/input/vbml/justifies_content_vertically_three_chars.json new file mode 100644 index 0000000..6cd9732 --- /dev/null +++ b/test/input/vbml/justifies_content_vertically_three_chars.json @@ -0,0 +1,16 @@ +{ + "style": { + "height": 5, + "width": 1 + }, + "components": [ + { + "template": "abc", + "style": { + "height": 5, + "width": 1, + "align": "justified" + } + } + ] +} diff --git a/test/input/vbml/layouts_absolute_components_by_relative_components.json b/test/input/vbml/layouts_absolute_components_by_relative_components.json new file mode 100644 index 0000000..53135e6 --- /dev/null +++ b/test/input/vbml/layouts_absolute_components_by_relative_components.json @@ -0,0 +1,30 @@ +{ + "style": { + "height": 22, + "width": 6 + }, + "components": [ + { + "template": "abc", + "style": { + "height": 6, + "width": 22, + "align": "top", + "justify": "left" + } + }, + { + "template": "def", + "style": { + "height": 1, + "width": 3, + "align": "top", + "justify": "left", + "absolutePosition": { + "x": 3, + "y": 0 + } + } + } + ] +} diff --git a/test/input/vbml/layouts_absolute_components_with_raw_components_for_mountain_clock.json b/test/input/vbml/layouts_absolute_components_with_raw_components_for_mountain_clock.json new file mode 100644 index 0000000..217f0f0 --- /dev/null +++ b/test/input/vbml/layouts_absolute_components_with_raw_components_for_mountain_clock.json @@ -0,0 +1,170 @@ +{ + "props": { + "time": "12:00 PM" + }, + "style": { + "height": 6, + "width": 22 + }, + "components": [ + { + "rawCharacters": [ + [ + 68, + 68, + 68, + 68, + 68, + 69, + 69, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68 + ], + [ + 68, + 68, + 68, + 68, + 69, + 69, + 69, + 69, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 68, + 65, + 65, + 65, + 65, + 68, + 68 + ], + [ + 63, + 63, + 63, + 69, + 66, + 69, + 66, + 69, + 69, + 63, + 63, + 63, + 63, + 63, + 63, + 65, + 65, + 65, + 65, + 65, + 65, + 63 + ], + [ + 63, + 63, + 66, + 66, + 66, + 69, + 66, + 66, + 66, + 66, + 63, + 63, + 63, + 63, + 63, + 65, + 65, + 65, + 65, + 65, + 65, + 63 + ], + [ + 64, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 64, + 64, + 64, + 64, + 64, + 65, + 65, + 65, + 65, + 64, + 64 + ], + [ + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 66, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64 + ] + ] + }, + { + "template": "{{time}}", + "style": { + "height": 1, + "width": 8, + "absolutePosition": { + "x": 11, + "y": 3 + } + } + } + ] +} diff --git a/test/input/vbml/layouts_absolute_over_relative_components.json b/test/input/vbml/layouts_absolute_over_relative_components.json new file mode 100644 index 0000000..6b0058a --- /dev/null +++ b/test/input/vbml/layouts_absolute_over_relative_components.json @@ -0,0 +1,30 @@ +{ + "style": { + "height": 22, + "width": 6 + }, + "components": [ + { + "template": "abc", + "style": { + "height": 6, + "width": 22, + "align": "top", + "justify": "left" + } + }, + { + "template": "def", + "style": { + "height": 1, + "width": 3, + "align": "top", + "justify": "left", + "absolutePosition": { + "x": 0, + "y": 0 + } + } + } + ] +} diff --git a/test/input/vbml/layouts_absolute_over_relative_components_standard_size.json b/test/input/vbml/layouts_absolute_over_relative_components_standard_size.json new file mode 100644 index 0000000..70bfecc --- /dev/null +++ b/test/input/vbml/layouts_absolute_over_relative_components_standard_size.json @@ -0,0 +1,30 @@ +{ + "style": { + "height": 6, + "width": 22 + }, + "components": [ + { + "template": "abc", + "style": { + "height": 6, + "width": 22, + "align": "top", + "justify": "left" + } + }, + { + "template": "def", + "style": { + "height": 1, + "width": 3, + "align": "top", + "justify": "left", + "absolutePosition": { + "x": 0, + "y": 0 + } + } + } + ] +} diff --git a/test/input/vbml/layouts_calendar_component_for_christmas.json b/test/input/vbml/layouts_calendar_component_for_christmas.json new file mode 100644 index 0000000..f151a2f --- /dev/null +++ b/test/input/vbml/layouts_calendar_component_for_christmas.json @@ -0,0 +1,24 @@ +{ + "style": { + "height": 6, + "width": 22 + }, + "components": [ + { + "calendar": { + "defaultDayColor": 66, + "month": "12", + "year": "2024", + "days": { + "25": 63 + } + }, + "style": { + "absolutePosition": { + "x": 0, + "y": 0 + } + } + } + ] +} diff --git a/test/input/vbml/layouts_calendar_component_on_the_right.json b/test/input/vbml/layouts_calendar_component_on_the_right.json new file mode 100644 index 0000000..c4a98e4 --- /dev/null +++ b/test/input/vbml/layouts_calendar_component_on_the_right.json @@ -0,0 +1,35 @@ +{ + "style": { + "height": 6, + "width": 22 + }, + "components": [ + { + "template": "Merry Christmas", + "style": { + "height": 6, + "width": 10, + "absolutePosition": { + "x": 0, + "y": 0 + } + } + }, + { + "calendar": { + "defaultDayColor": 66, + "month": "12", + "year": "2028", + "days": { + "25": 63 + } + }, + "style": { + "absolutePosition": { + "x": 10, + "y": 0 + } + } + } + ] +} diff --git a/test/input/vbml/layouts_calendar_component_with_other_components.json b/test/input/vbml/layouts_calendar_component_with_other_components.json new file mode 100644 index 0000000..b2135ed --- /dev/null +++ b/test/input/vbml/layouts_calendar_component_with_other_components.json @@ -0,0 +1,40 @@ +{ + "style": { + "height": 6, + "width": 22 + }, + "components": [ + { + "template": "December 2024 Calendar", + "style": { + "height": 6, + "width": 10, + "absolutePosition": { + "x": 13, + "y": 0 + } + } + }, + { + "calendar": { + "month": "12", + "year": "2024", + "days": { + "1": 63, + "2": 64, + "3": 65, + "4": 66, + "5": 67, + "6": 68, + "7": 63 + } + }, + "style": { + "absolutePosition": { + "x": 0, + "y": 0 + } + } + } + ] +} diff --git a/test/input/vbml/layouts_components_side_by_side.json b/test/input/vbml/layouts_components_side_by_side.json new file mode 100644 index 0000000..8dfc558 --- /dev/null +++ b/test/input/vbml/layouts_components_side_by_side.json @@ -0,0 +1,22 @@ +{ + "style": { + "height": 1, + "width": 4 + }, + "components": [ + { + "template": "hi", + "style": { + "width": 2, + "height": 1 + } + }, + { + "template": "hi", + "style": { + "width": 2, + "height": 1 + } + } + ] +} diff --git a/test/input/vbml/layouts_components_vertically.json b/test/input/vbml/layouts_components_vertically.json new file mode 100644 index 0000000..99be915 --- /dev/null +++ b/test/input/vbml/layouts_components_vertically.json @@ -0,0 +1,22 @@ +{ + "style": { + "height": 2, + "width": 2 + }, + "components": [ + { + "template": "hi", + "style": { + "width": 2, + "height": 1 + } + }, + { + "template": "hi", + "style": { + "width": 2, + "height": 1 + } + } + ] +} diff --git a/test/input/vbml/layouts_minimalist_calendar_component.json b/test/input/vbml/layouts_minimalist_calendar_component.json new file mode 100644 index 0000000..4459682 --- /dev/null +++ b/test/input/vbml/layouts_minimalist_calendar_component.json @@ -0,0 +1,27 @@ +{ + "style": { + "height": 6, + "width": 22 + }, + "components": [ + { + "style": { + "absolutePosition": { + "x": 0, + "y": 0 + } + }, + "calendar": { + "defaultDayColor": 66, + "hideDates": true, + "hideMonthYear": true, + "hideSMTWTFS": true, + "month": "12", + "year": "2024", + "days": { + "25": 63 + } + } + } + ] +} diff --git a/test/input/vbml/layouts_raw_components.json b/test/input/vbml/layouts_raw_components.json new file mode 100644 index 0000000..dbf1a1f --- /dev/null +++ b/test/input/vbml/layouts_raw_components.json @@ -0,0 +1,17 @@ +{ + "style": { + "height": 6, + "width": 22 + }, + "components": [ + { + "rawCharacters": [ + [ + 1, + 2, + 3 + ] + ] + } + ] +} diff --git a/test/input/vbml/parses_single_component_on_a_board.json b/test/input/vbml/parses_single_component_on_a_board.json new file mode 100644 index 0000000..56b81f6 --- /dev/null +++ b/test/input/vbml/parses_single_component_on_a_board.json @@ -0,0 +1,11 @@ +{ + "style": { + "height": 1, + "width": 2 + }, + "components": [ + { + "template": "hi" + } + ] +} diff --git a/test/input/vbml/respects_double_returns.json b/test/input/vbml/respects_double_returns.json new file mode 100644 index 0000000..6e30f7e --- /dev/null +++ b/test/input/vbml/respects_double_returns.json @@ -0,0 +1,15 @@ +{ + "style": { + "height": 3, + "width": 2 + }, + "components": [ + { + "template": "h\n\ni", + "style": { + "align": "top", + "justify": "left" + } + } + ] +} diff --git a/test/input/vbml/respects_triple_returns.json b/test/input/vbml/respects_triple_returns.json new file mode 100644 index 0000000..063ea0d --- /dev/null +++ b/test/input/vbml/respects_triple_returns.json @@ -0,0 +1,15 @@ +{ + "style": { + "height": 4, + "width": 2 + }, + "components": [ + { + "template": "h\n\n\ni", + "style": { + "align": "top", + "justify": "left" + } + } + ] +} diff --git a/test/platform-exceptions/php/vbml/layouts_absolute_components_by_relative_components.json b/test/platform-exceptions/php/vbml/layouts_absolute_components_by_relative_components.json new file mode 100644 index 0000000..422ea96 --- /dev/null +++ b/test/platform-exceptions/php/vbml/layouts_absolute_components_by_relative_components.json @@ -0,0 +1,27 @@ +{ + "reason": "PHP truncates these 22x6 absolute layout cases to 6-column rows instead of preserving Node's current mixed-width board shape.", + "result": [ + [1, 2, 3, 4, 5, 6], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/platform-exceptions/php/vbml/layouts_absolute_over_relative_components.json b/test/platform-exceptions/php/vbml/layouts_absolute_over_relative_components.json new file mode 100644 index 0000000..500d598 --- /dev/null +++ b/test/platform-exceptions/php/vbml/layouts_absolute_over_relative_components.json @@ -0,0 +1,27 @@ +{ + "reason": "PHP truncates these 22x6 absolute layout cases to 6-column rows instead of preserving Node's current mixed-width board shape.", + "result": [ + [4, 5, 6, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/platform-exceptions/python/classic/converts_special_characters.json b/test/platform-exceptions/python/classic/converts_special_characters.json new file mode 100644 index 0000000..58ef8d7 --- /dev/null +++ b/test/platform-exceptions/python/classic/converts_special_characters.json @@ -0,0 +1,11 @@ +{ + "reason": "Python classic wrapping diverges from Node when unsupported glyphs appear inside a long token sequence.", + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [37, 38, 39, 40, 54, 0, 47, 0, 41, 42, 46, 1, 19, 19, 0, 6, 0, 3, 0, 41, 42, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/platform-exceptions/python/hasSpecialCharacters/excludes_ios_single_quote.json b/test/platform-exceptions/python/hasSpecialCharacters/excludes_ios_single_quote.json new file mode 100644 index 0000000..d97b551 --- /dev/null +++ b/test/platform-exceptions/python/hasSpecialCharacters/excludes_ios_single_quote.json @@ -0,0 +1,4 @@ +{ + "reason": "Python does not currently treat the left single quote character as supported input.", + "result": true +} diff --git a/test/platform-exceptions/python/vbml/layouts_absolute_components_by_relative_components.json b/test/platform-exceptions/python/vbml/layouts_absolute_components_by_relative_components.json new file mode 100644 index 0000000..e55bf5f --- /dev/null +++ b/test/platform-exceptions/python/vbml/layouts_absolute_components_by_relative_components.json @@ -0,0 +1,27 @@ +{ + "reason": "Python truncates these 22x6 absolute layout cases to 6-column rows instead of preserving Node's current mixed-width board shape.", + "result": [ + [1, 2, 3, 4, 5, 6], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/platform-exceptions/python/vbml/layouts_absolute_over_relative_components.json b/test/platform-exceptions/python/vbml/layouts_absolute_over_relative_components.json new file mode 100644 index 0000000..8559189 --- /dev/null +++ b/test/platform-exceptions/python/vbml/layouts_absolute_over_relative_components.json @@ -0,0 +1,27 @@ +{ + "reason": "Python truncates these 22x6 absolute layout cases to 6-column rows instead of preserving Node's current mixed-width board shape.", + "result": [ + [4, 5, 6, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/platform-exceptions/python/vbml/layouts_calendar_component_for_christmas.json b/test/platform-exceptions/python/vbml/layouts_calendar_component_for_christmas.json new file mode 100644 index 0000000..545e8a0 --- /dev/null +++ b/test/platform-exceptions/python/vbml/layouts_calendar_component_for_christmas.json @@ -0,0 +1,6 @@ +{ + "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", + "error": { + "message": "'<=' not supported between instances of 'int' and 'str'" + } +} diff --git a/test/platform-exceptions/python/vbml/layouts_calendar_component_on_the_right.json b/test/platform-exceptions/python/vbml/layouts_calendar_component_on_the_right.json new file mode 100644 index 0000000..545e8a0 --- /dev/null +++ b/test/platform-exceptions/python/vbml/layouts_calendar_component_on_the_right.json @@ -0,0 +1,6 @@ +{ + "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", + "error": { + "message": "'<=' not supported between instances of 'int' and 'str'" + } +} diff --git a/test/platform-exceptions/python/vbml/layouts_calendar_component_with_other_components.json b/test/platform-exceptions/python/vbml/layouts_calendar_component_with_other_components.json new file mode 100644 index 0000000..545e8a0 --- /dev/null +++ b/test/platform-exceptions/python/vbml/layouts_calendar_component_with_other_components.json @@ -0,0 +1,6 @@ +{ + "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", + "error": { + "message": "'<=' not supported between instances of 'int' and 'str'" + } +} diff --git a/test/platform-exceptions/python/vbml/layouts_minimalist_calendar_component.json b/test/platform-exceptions/python/vbml/layouts_minimalist_calendar_component.json new file mode 100644 index 0000000..545e8a0 --- /dev/null +++ b/test/platform-exceptions/python/vbml/layouts_minimalist_calendar_component.json @@ -0,0 +1,6 @@ +{ + "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", + "error": { + "message": "'<=' not supported between instances of 'int' and 'str'" + } +} From ff2362ce505e07410a777b6cc9b02cb07a14cb41 Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Wed, 18 Mar 2026 23:37:30 -0700 Subject: [PATCH 02/12] test(conformance): add shared fixture validator --- package.json | 1 + scripts/validate-conformance-fixtures.mjs | 198 ++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 scripts/validate-conformance-fixtures.mjs diff --git a/package.json b/package.json index c84a8cc..8aac406 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "lib/index.js", "scripts": { "build": "tsc", + "test:fixtures": "node scripts/validate-conformance-fixtures.mjs", "test": "jest" }, "repository": { diff --git a/scripts/validate-conformance-fixtures.mjs b/scripts/validate-conformance-fixtures.mjs new file mode 100644 index 0000000..4b0644c --- /dev/null +++ b/scripts/validate-conformance-fixtures.mjs @@ -0,0 +1,198 @@ +/** + * Validates the shared conformance fixture tree under `test/`. + * + * The script treats `test/input//.json` and + * `test/expected//.json` as the canonical shared cases and + * verifies that every case has both halves. It also validates the shape of + * expected fixtures and platform-specific exceptions in + * `test/platform-exceptions///.json`. + * + * In practice this catches: + * - missing input/expected pairs + * - invalid JSON + * - malformed expected error payloads + * - malformed platform exceptions + * - platform exceptions that do not map to a shared case + * - accidental Node exceptions + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const repoRoot = path.resolve(__dirname, ".."); +const inputRoot = path.join(repoRoot, "test", "input"); +const expectedRoot = path.join(repoRoot, "test", "expected"); +const platformExceptionRoot = path.join(repoRoot, "test", "platform-exceptions"); + +const errors = []; + +const isPlainObject = (value) => + typeof value === "object" && value !== null && !Array.isArray(value); + +const walkJsonFiles = (rootDir) => { + if (!fs.existsSync(rootDir)) { + return []; + } + + return fs + .readdirSync(rootDir, { + withFileTypes: true, + }) + .flatMap((entry) => { + const fullPath = path.join(rootDir, entry.name); + + if (entry.isDirectory()) { + return walkJsonFiles(fullPath); + } + + return entry.name.endsWith(".json") ? [fullPath] : []; + }) + .sort(); +}; + +const toCaseId = (rootDir, filePath) => + path.relative(rootDir, filePath).replace(/\.json$/, "").split(path.sep).join("/"); + +const readJsonFile = (filePath) => { + try { + return JSON.parse(fs.readFileSync(filePath, "utf8")); + } catch (error) { + errors.push(`${path.relative(repoRoot, filePath)} is not valid JSON: ${error.message}`); + return null; + } +}; + +const validateErrorExpectation = (fileLabel, value) => { + if (!isPlainObject(value)) { + errors.push(`${fileLabel} must define "error" as an object.`); + return; + } + + if (typeof value.message !== "string" || !value.message.trim()) { + errors.push(`${fileLabel} must define "error.message" as a non-empty string.`); + } +}; + +const validateExpectedFixture = (filePath, payload) => { + const fileLabel = path.relative(repoRoot, filePath); + + if (!isPlainObject(payload)) { + errors.push(`${fileLabel} must contain a JSON object.`); + return; + } + + const hasResult = Object.hasOwn(payload, "result"); + const hasError = Object.hasOwn(payload, "error"); + + if (hasResult === hasError) { + errors.push(`${fileLabel} must define exactly one of "result" or "error".`); + return; + } + + if (hasError) { + validateErrorExpectation(fileLabel, payload.error); + } +}; + +const validatePlatformException = (filePath, payload, knownCases) => { + const relativePath = path.relative(platformExceptionRoot, filePath); + const fileLabel = path.relative(repoRoot, filePath); + const segments = relativePath.split(path.sep); + + if (segments.length < 3) { + errors.push(`${fileLabel} must be stored as //.json.`); + return; + } + + const [platform, ...caseSegments] = segments; + const caseId = caseSegments.join("/").replace(/\.json$/, ""); + + if (platform === "node") { + errors.push(`${fileLabel} is invalid because Node is the shared source of truth.`); + } + + if (!knownCases.has(caseId)) { + errors.push(`${fileLabel} does not match a shared case in test/input and test/expected.`); + } + + if (!isPlainObject(payload)) { + errors.push(`${fileLabel} must contain a JSON object.`); + return; + } + + if (typeof payload.reason !== "string" || !payload.reason.trim()) { + errors.push(`${fileLabel} must define "reason" as a non-empty string.`); + } + + const hasResult = Object.hasOwn(payload, "result"); + const hasError = Object.hasOwn(payload, "error"); + const hasSkip = Object.hasOwn(payload, "skip"); + const expectationCount = [hasResult, hasError, hasSkip].filter(Boolean).length; + + if (expectationCount !== 1) { + errors.push(`${fileLabel} must define exactly one of "result", "error", or "skip".`); + return; + } + + if (hasError) { + validateErrorExpectation(fileLabel, payload.error); + } + + if (hasSkip && payload.skip !== true) { + errors.push(`${fileLabel} must define "skip": true when skipping a case.`); + } +}; + +const inputFiles = walkJsonFiles(inputRoot); +const expectedFiles = walkJsonFiles(expectedRoot); +const platformExceptionFiles = walkJsonFiles(platformExceptionRoot); + +const inputCases = new Set(inputFiles.map((filePath) => toCaseId(inputRoot, filePath))); +const expectedCases = new Set( + expectedFiles.map((filePath) => toCaseId(expectedRoot, filePath)) +); + +for (const caseId of inputCases) { + if (!expectedCases.has(caseId)) { + errors.push(`Missing expected fixture for test/input/${caseId}.json.`); + } +} + +for (const caseId of expectedCases) { + if (!inputCases.has(caseId)) { + errors.push(`Missing input fixture for test/expected/${caseId}.json.`); + } +} + +for (const expectedFile of expectedFiles) { + const payload = readJsonFile(expectedFile); + if (payload !== null) { + validateExpectedFixture(expectedFile, payload); + } +} + +const knownCases = new Set( + [...inputCases].filter((caseId) => expectedCases.has(caseId)) +); + +for (const exceptionFile of platformExceptionFiles) { + const payload = readJsonFile(exceptionFile); + if (payload !== null) { + validatePlatformException(exceptionFile, payload, knownCases); + } +} + +if (errors.length > 0) { + console.error("Conformance fixture validation failed:"); + for (const error of errors) { + console.error(`- ${error}`); + } + process.exit(1); +} + +console.log( + `Validated ${knownCases.size} shared cases and ${platformExceptionFiles.length} platform exceptions.` +); From 6cdda95c4526f1e151ddc3c319d00b7644a8dd76 Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Wed, 18 Mar 2026 23:42:46 -0700 Subject: [PATCH 03/12] docs(test): add conformance maintenance guide --- test/README.md | 76 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/test/README.md b/test/README.md index 9208465..10cd0ad 100644 --- a/test/README.md +++ b/test/README.md @@ -1,17 +1,65 @@ -# Shared Test Fixtures +# Shared Conformance Fixtures -The `test/input` and `test/expected` trees define the shared behavioral -contract for the Node, Python, and PHP implementations. +The `test/` directory is data-only. It defines the shared behavioral contract +for the Node, Python, and PHP implementations. -- Keep matching relative paths between `test/input//...` and - `test/expected//...`. -- Use the filename as the test name. +Node is the source of truth. Shared expectations in `test/expected` should +match Node behavior unless the Node suite itself is being corrected. + +## Layout + +- `test/input//.json` +- `test/expected//.json` +- `test/platform-exceptions///.json` + +The executable runners live outside of `test/`: + +- Node: `src/__tests__/conformance` +- Python: `python/tests/conformance` +- PHP: `php/tests/Conformance` + +## Shared Case Rules + +- Keep matching relative paths between `test/input` and `test/expected`. +- Use the filename as the case name. - Store the input payload directly in `test/input`. -- Store either `{ "result": ... }` or `{ "error": { "message": "..." } }` - in `test/expected`. -- Put platform differences under - `test/expected/overrides///...`. -- Every override must include `kind` and `reason`, and then exactly one of - `result`, `error`, or `skip`. -- Prefer shared behavior coverage. Keep local-only tests only when output files - cannot express the contract cleanly, such as deep-copy identity. +- Store exactly one of these shapes in `test/expected`: + - `{ "result": ... }` + - `{ "error": { "message": "..." } }` + +## Platform Exception Rules + +Use `test/platform-exceptions` only when a non-Node platform intentionally or +currently behaves differently from the shared expectation. + +- Never add platform exceptions for Node. +- Every platform exception must include a non-empty `reason`. +- Every platform exception must define exactly one of `result`, `error`, or + `skip`. +- `skip` must be written as `{ "skip": true }`. +- Treat platform exceptions as temporary documentation of drift, not as an + alternate test suite. + +## When To Add Shared Cases + +Prefer shared conformance coverage for supported behavior. Keep platform-native +tests only when the contract is not cleanly expressible as input and expected +output, such as deep-copy identity. + +## Maintainer Workflow + +1. Add or update `test/input//.json`. +2. Add the matching `test/expected//.json` using Node behavior as + the default. +3. If Python or PHP differs, add a matching file under + `test/platform-exceptions///.json` with a `reason`. +4. Remove the platform exception when that platform matches the shared contract. +5. Run the fixture validator and the affected platform suites. + +## Commands + +- Validate shared fixtures: `yarn test:fixtures` +- Run Node tests: `yarn test --runInBand` +- Run Python conformance tests: + `PYTHONPATH=python pytest python/tests/conformance -q` +- Run PHP tests: `cd php && ./vendor/bin/phpunit` From 3b8e7b37d8dfd4ced382c1b732d2225072e5d4ff Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Wed, 18 Mar 2026 22:41:13 -0700 Subject: [PATCH 04/12] test(conformance): migrate python suites to shared fixtures --- python/tests/conformance/__init__.py | 1 + python/tests/conformance/support.py | 144 ++++ python/tests/conformance/test_calendar.py | 23 + .../test_character_codes_to_ascii.py | 18 + .../test_character_codes_to_string.py | 18 + python/tests/conformance/test_classic.py | 20 + .../test_has_special_characters.py | 12 + .../tests/conformance/test_parse_component.py | 29 + .../test_sanitize_special_characters.py | 14 + python/tests/conformance/test_vbml.py | 12 + python/tests/test_calendar.py | 248 ------ python/tests/test_character_codes.py | 10 - python/tests/test_character_codes_to_ascii.py | 26 - .../tests/test_character_codes_to_string.py | 103 --- python/tests/test_classic.py | 201 ----- python/tests/test_copy_character_codes.py | 21 - python/tests/test_create_empty_board.py | 11 - python/tests/test_has_special_characters.py | 78 -- python/tests/test_horizontal_align.py | 20 - python/tests/test_parse_component.py | 477 ----------- python/tests/test_random_colors.py | 34 - .../tests/test_sanitize_special_characters.py | 149 ---- python/tests/test_vbml.py | 806 ------------------ python/tests/test_vertical_align.py | 15 - 24 files changed, 291 insertions(+), 2199 deletions(-) create mode 100644 python/tests/conformance/__init__.py create mode 100644 python/tests/conformance/support.py create mode 100644 python/tests/conformance/test_calendar.py create mode 100644 python/tests/conformance/test_character_codes_to_ascii.py create mode 100644 python/tests/conformance/test_character_codes_to_string.py create mode 100644 python/tests/conformance/test_classic.py create mode 100644 python/tests/conformance/test_has_special_characters.py create mode 100644 python/tests/conformance/test_parse_component.py create mode 100644 python/tests/conformance/test_sanitize_special_characters.py create mode 100644 python/tests/conformance/test_vbml.py delete mode 100644 python/tests/test_calendar.py delete mode 100644 python/tests/test_character_codes.py delete mode 100644 python/tests/test_character_codes_to_ascii.py delete mode 100644 python/tests/test_character_codes_to_string.py delete mode 100644 python/tests/test_classic.py delete mode 100644 python/tests/test_copy_character_codes.py delete mode 100644 python/tests/test_create_empty_board.py delete mode 100644 python/tests/test_has_special_characters.py delete mode 100644 python/tests/test_horizontal_align.py delete mode 100644 python/tests/test_parse_component.py delete mode 100644 python/tests/test_random_colors.py delete mode 100644 python/tests/test_sanitize_special_characters.py delete mode 100644 python/tests/test_vbml.py delete mode 100644 python/tests/test_vertical_align.py diff --git a/python/tests/conformance/__init__.py b/python/tests/conformance/__init__.py new file mode 100644 index 0000000..87564a7 --- /dev/null +++ b/python/tests/conformance/__init__.py @@ -0,0 +1 @@ +"""Python shared conformance tests.""" diff --git a/python/tests/conformance/support.py b/python/tests/conformance/support.py new file mode 100644 index 0000000..c4f8215 --- /dev/null +++ b/python/tests/conformance/support.py @@ -0,0 +1,144 @@ +"""Shared conformance helpers for Python.""" + +from __future__ import annotations + +import json +import re +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Callable + +import pytest + + +@dataclass +class ResolvedExpected: + """Resolved expected outcome for a test case.""" + + result: Any | None = None + error: str | None = None + skip: str | None = None + + +@dataclass +class ConformanceCase: + """A shared conformance case.""" + + id: str + input_data: Any + expected: ResolvedExpected + + +_REPO_ROOT = Path(__file__).resolve().parents[3] +_INPUT_ROOT = _REPO_ROOT / "test" / "input" +_EXPECTED_ROOT = _REPO_ROOT / "test" / "expected" +_PLATFORM_EXCEPTION_ROOT = _REPO_ROOT / "test" / "platform-exceptions" + + +def _read_json(path: Path) -> Any: + return json.loads(path.read_text()) + + +def _walk_json_files(root: Path) -> list[Path]: + return sorted(path for path in root.rglob("*.json") if path.is_file()) + + +def _resolve_expected( + suite: str, + case_id: str, + payload: dict[str, Any], + platform: str, +) -> ResolvedExpected: + exception_path = _PLATFORM_EXCEPTION_ROOT / platform / suite / f"{case_id}.json" + + if exception_path.exists(): + exception = _read_json(exception_path) + reason = str(exception.get("reason", "")).strip() + if not reason: + raise ValueError( + f'Platform exception "{suite}/{case_id}" is missing a reason.' + ) + + if exception.get("skip"): + if "result" in exception or "error" in exception: + raise ValueError( + f'Platform exception "{suite}/{case_id}" cannot define ' + "skip with result or error." + ) + + return ResolvedExpected(skip=reason) + + has_result = "result" in exception + has_error = "error" in exception + if has_result == has_error: + raise ValueError( + f'Platform exception "{suite}/{case_id}" must define exactly ' + "one of result or error." + ) + + return ResolvedExpected( + result=exception.get("result"), + error=exception.get("error", {}).get("message"), + ) + + has_result = "result" in payload + has_error = "error" in payload + if has_result == has_error: + raise ValueError( + f'Conformance case "{suite}/{case_id}" must define exactly one of ' + "result or error." + ) + + return ResolvedExpected( + result=payload.get("result"), + error=payload.get("error", {}).get("message"), + ) + + +def load_cases(suite: str, platform: str = "python") -> list[ConformanceCase]: + """Load all cases for a suite.""" + + input_root = _INPUT_ROOT / suite + expected_root = _EXPECTED_ROOT / suite + + cases: list[ConformanceCase] = [] + for input_path in _walk_json_files(input_root): + relative_path = input_path.relative_to(input_root) + expected_path = expected_root / relative_path + + if not expected_path.exists(): + raise FileNotFoundError(f"Missing expected file for {suite}/{relative_path}") + + case_id = str(relative_path.with_suffix("")).replace("\\", "/") + input_data = _read_json(input_path) + expected_payload = _read_json(expected_path) + + cases.append( + ConformanceCase( + id=case_id, + input_data=input_data, + expected=_resolve_expected(suite, case_id, expected_payload, platform), + ) + ) + + return cases + + +def case_ids(case: ConformanceCase) -> str: + """Stable pytest case ids.""" + + return case.id + + +def assert_case(case: ConformanceCase, run: Callable[[Any], Any]) -> None: + """Run and assert a shared conformance case.""" + + if case.expected.skip: + pytest.skip(case.expected.skip) + + if case.expected.error: + with pytest.raises(Exception, match=re.escape(case.expected.error)): + run(case.input_data) + return + + assert run(case.input_data) == case.expected.result diff --git a/python/tests/conformance/test_calendar.py b/python/tests/conformance/test_calendar.py new file mode 100644 index 0000000..f09baec --- /dev/null +++ b/python/tests/conformance/test_calendar.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +import pytest + +from pyvbml.calendar import make_calendar + +from .support import assert_case, case_ids, load_cases + + +@pytest.mark.parametrize("case", load_cases("calendar"), ids=case_ids) +def test_calendar(case) -> None: + def _run(input_data): + return make_calendar( + int(input_data["month"]), + int(input_data["year"]), + default_day_color=input_data.get("defaultDayColor"), + highlighted_days=input_data.get("days"), + hide_day_of_week=input_data.get("hideSMTWTFS", False), + hide_dates=input_data.get("hideDates", False), + hide_month_year=input_data.get("hideMonthYear", False), + ) + + assert_case(case, _run) diff --git a/python/tests/conformance/test_character_codes_to_ascii.py b/python/tests/conformance/test_character_codes_to_ascii.py new file mode 100644 index 0000000..95b452f --- /dev/null +++ b/python/tests/conformance/test_character_codes_to_ascii.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import pytest + +from pyvbml.character_codes_to_ascii import character_codes_to_ascii + +from .support import assert_case, case_ids, load_cases + + +@pytest.mark.parametrize("case", load_cases("characterCodesToAscii"), ids=case_ids) +def test_character_codes_to_ascii(case) -> None: + assert_case( + case, + lambda input_data: character_codes_to_ascii( + input_data["characterCodes"], + input_data.get("isWhite", False), + ), + ) diff --git a/python/tests/conformance/test_character_codes_to_string.py b/python/tests/conformance/test_character_codes_to_string.py new file mode 100644 index 0000000..27a1a3b --- /dev/null +++ b/python/tests/conformance/test_character_codes_to_string.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import pytest + +from pyvbml.character_codes_to_string import character_codes_to_string + +from .support import assert_case, case_ids, load_cases + + +@pytest.mark.parametrize("case", load_cases("characterCodesToString"), ids=case_ids) +def test_character_codes_to_string(case) -> None: + assert_case( + case, + lambda input_data: character_codes_to_string( + input_data["characters"], + input_data.get("options"), + ), + ) diff --git a/python/tests/conformance/test_classic.py b/python/tests/conformance/test_classic.py new file mode 100644 index 0000000..162dec6 --- /dev/null +++ b/python/tests/conformance/test_classic.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +import pytest + +from pyvbml.classic import classic + +from .support import assert_case, case_ids, load_cases + + +@pytest.mark.parametrize("case", load_cases("classic"), ids=case_ids) +def test_classic(case) -> None: + def _run(input_data): + options = input_data.get("options", {}) + return classic( + input_data["text"], + extra_h_padding=options.get("extraHPadding", 0), + preserve_double_spaces=options.get("preserveDoubleSpaces", False), + ) + + assert_case(case, _run) diff --git a/python/tests/conformance/test_has_special_characters.py b/python/tests/conformance/test_has_special_characters.py new file mode 100644 index 0000000..e0504a4 --- /dev/null +++ b/python/tests/conformance/test_has_special_characters.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +import pytest + +from pyvbml.has_special_characters import has_special_characters + +from .support import assert_case, case_ids, load_cases + + +@pytest.mark.parametrize("case", load_cases("hasSpecialCharacters"), ids=case_ids) +def test_has_special_characters(case) -> None: + assert_case(case, lambda input_data: has_special_characters(input_data["text"])) diff --git a/python/tests/conformance/test_parse_component.py b/python/tests/conformance/test_parse_component.py new file mode 100644 index 0000000..98067d3 --- /dev/null +++ b/python/tests/conformance/test_parse_component.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import pytest + +from pyvbml.parse_component import parse_absolute_component, parse_component + +from .support import assert_case, case_ids, load_cases + + +@pytest.mark.parametrize("case", load_cases("parseComponent"), ids=case_ids) +def test_parse_component(case) -> None: + def _run(input_data): + props = input_data.get("props", {}) + if input_data.get("mode") == "absolute": + return parse_absolute_component( + input_data["height"], + input_data["width"], + props, + input_data["component"], + ) + + return parse_component( + input_data["height"], + input_data["width"], + props, + input_data["component"], + ) + + assert_case(case, _run) diff --git a/python/tests/conformance/test_sanitize_special_characters.py b/python/tests/conformance/test_sanitize_special_characters.py new file mode 100644 index 0000000..ecceb1b --- /dev/null +++ b/python/tests/conformance/test_sanitize_special_characters.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +import pytest + +from pyvbml.sanitize_special_characters import sanitize_special_characters + +from .support import assert_case, case_ids, load_cases + + +@pytest.mark.parametrize( + "case", load_cases("sanitizeSpecialCharacters"), ids=case_ids +) +def test_sanitize_special_characters(case) -> None: + assert_case(case, lambda input_data: sanitize_special_characters(input_data["text"])) diff --git a/python/tests/conformance/test_vbml.py b/python/tests/conformance/test_vbml.py new file mode 100644 index 0000000..1666a19 --- /dev/null +++ b/python/tests/conformance/test_vbml.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +import pytest + +from pyvbml import vbml + +from .support import assert_case, case_ids, load_cases + + +@pytest.mark.parametrize("case", load_cases("vbml"), ids=case_ids) +def test_vbml(case) -> None: + assert_case(case, lambda input_data: vbml.parse(input_data)) diff --git a/python/tests/test_calendar.py b/python/tests/test_calendar.py deleted file mode 100644 index 3df97b8..0000000 --- a/python/tests/test_calendar.py +++ /dev/null @@ -1,248 +0,0 @@ -"""Test calendar.""" - -from __future__ import annotations - -import pytest - -from pyvbml.calendar import CharacterCode, make_calendar - - -def test_calendar() -> None: - """Test calendar.""" - result = make_calendar(6, 2026) - - assert result == [ - [32, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 44, 29, 36, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_calendar_with_4_weeks() -> None: - """Test calendar with only 4 weeks.""" - result = make_calendar(2, 2026) - - assert result == [ - [28, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_calendar_with_6_weeks() -> None: - """Test calendar with 6 weeks.""" - result = make_calendar(8, 2026) - - assert result == [ - [34, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 28, 44, 34, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 35, 44, 27, 31, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 32, 44, 28, 28, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 28, - 29, - 44, - 29, - 27, - 65, - 65, - 65, - 65, - 65, - 65, - 65, - 65, - 65, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ], - ] - - -def test_calendar_with_1_day_on_last_week() -> None: - """Test calendar renders single date header when last week has one day.""" - result = make_calendar(2, 2027) - - assert result == [ - [28, 59, 28, 33, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_calendar_with_default_day_color() -> None: - """Test calendar with default day color.""" - result = make_calendar(6, 2026, default_day_color=CharacterCode.RED) - - assert result == [ - [32, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 63, 63, 63, 63, 63, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 63, 63, 63, 63, 63, 63, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 63, 63, 63, 63, 63, 63, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 63, 63, 63, 63, 63, 63, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 44, 29, 36, 63, 63, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_calendar_with_highlighted_days() -> None: - """Test calendar with highlighted days.""" - result = make_calendar( - 6, 2026, highlighted_days={18: CharacterCode.BLUE, 30: CharacterCode.WHITE} - ) - - assert result == [ - [32, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 67, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 44, 29, 36, 65, 65, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_calendar_does_not_highlight_days_outside_of_month() -> None: - """Test calendar doesn't highlight days outside of month.""" - result = make_calendar( - 6, - 2026, - highlighted_days={ - 30: CharacterCode.BLUE, - 31: CharacterCode.BLUE, - 32: CharacterCode.BLUE, - }, - ) - assert result == [ - [32, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 44, 29, 36, 65, 65, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - result = make_calendar( - 2, - 2026, - highlighted_days={ - 28: CharacterCode.BLUE, - 29: CharacterCode.BLUE, - 30: CharacterCode.BLUE, - 31: CharacterCode.BLUE, - 32: CharacterCode.BLUE, - }, - ) - assert result == [ - [28, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - result = make_calendar( - 8, - 2026, - highlighted_days={ - 28: CharacterCode.BLUE, - 29: CharacterCode.BLUE, - 30: CharacterCode.BLUE, - 31: CharacterCode.BLUE, - 32: CharacterCode.BLUE, - }, - ) - assert result == [ - [34, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 28, 44, 34, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 35, 44, 27, 31, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 32, 44, 28, 28, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 28, - 29, - 44, - 29, - 27, - 65, - 65, - 65, - 65, - 65, - 67, - 67, - 67, - 67, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ], - ] - - -def test_calendar_with_hidden_day_of_week() -> None: - """Test calendar with hidden day of week.""" - result = make_calendar(6, 2026, hide_day_of_week=True) - - assert result == [ - [32, 59, 28, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 44, 29, 36, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_calendar_with_hidden_dates() -> None: - """Test calendar with hidden dates.""" - result = make_calendar(6, 2026, hide_dates=True) - - assert result == [ - [32, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_calendar_with_hidden_month_year() -> None: - """Test calendar with hidden month/year.""" - result = make_calendar(6, 2026, hide_month_year=True) - - assert result == [ - [0, 0, 0, 0, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 44, 29, 36, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_calendar_with_invalid_month() -> None: - """Test calendar with invalid month.""" - with pytest.raises(ValueError): - make_calendar(0, 2026) diff --git a/python/tests/test_character_codes.py b/python/tests/test_character_codes.py deleted file mode 100644 index 702ec5e..0000000 --- a/python/tests/test_character_codes.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Test character codes.""" - -from __future__ import annotations - -from pyvbml.character_codes import convert_characters_to_character_codes - - -def test_dangling_bracket() -> None: - """Should drop dangling bracket.""" - assert convert_characters_to_character_codes("{66}}") == [66] diff --git a/python/tests/test_character_codes_to_ascii.py b/python/tests/test_character_codes_to_ascii.py deleted file mode 100644 index 1ea1d88..0000000 --- a/python/tests/test_character_codes_to_ascii.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Test character codes to ASCII. - -Port of Vestaboard/vbml/src/__tests__/characterCodesToAscii.spec.ts -""" - -from __future__ import annotations - -from pyvbml.character_codes_to_ascii import character_codes_to_ascii - - -def test_converts_color_codes() -> None: - """Should convert colors.""" - result = character_codes_to_ascii([[63, 64, 65, 66, 67, 68, 69, 70]]) - assert result == "šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›" - - -def test_handles_multiple_rows() -> None: - """Should handle rows.""" - result = character_codes_to_ascii([[63, 64], [63, 64]]) - assert result == "🟄🟧\n\n🟄🟧" - - -def test_spaces_out_letters() -> None: - """Should space out letters.""" - result = character_codes_to_ascii([[1, 2]]) - assert result == "A B " diff --git a/python/tests/test_character_codes_to_string.py b/python/tests/test_character_codes_to_string.py deleted file mode 100644 index 0da752e..0000000 --- a/python/tests/test_character_codes_to_string.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Test character codes to string. - -Port of Vestaboard/vbml/src/__tests__/characterCodesToString.spec.ts -""" - -from __future__ import annotations - -from pyvbml.character_codes_to_string import character_codes_to_string - - -def test_converts_word_to_string() -> None: - """Should convert a word to a string.""" - assert character_codes_to_string([[1, 2]]) == "AB" - - -def test_converts_two_line_sentence() -> None: - """Should convert two-line sentence.""" - result = character_codes_to_string( - [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 20, - 8, - 9, - 19, - 0, - 9, - 19, - 0, - 1, - 0, - 12, - 15, - 14, - 7, - 5, - 18, - 0, - 2, - 12, - 15, - 3, - 11, - ], - [ - 20, - 8, - 1, - 20, - 0, - 19, - 16, - 1, - 14, - 19, - 0, - 28, - 0, - 12, - 9, - 14, - 5, - 19, - 0, - 0, - 0, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - ) - assert result == "THIS IS A LONGER BLOCK THAT SPANS 2 LINES" - - -def test_handles_breaks_gracefully() -> None: - """Should handle breaks.""" - result = character_codes_to_string( - [ - [0, 0, 8, 1, 14, 4, 12, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 2, 18, 5, 1, 11, 19, 0, 7, 18, 1, 3, 5, 6, 21, 12, 12, 25, 0, 0, 0], - ] - ) - assert result == "HANDLE BREAKS GRACEFULLY" - - -def test_handles_line_breaks() -> None: - """Should handle line breaks.""" - result = character_codes_to_string( - [[1, 2, 0, 0, 0], [3, 4, 0, 0, 0]], - {"allowLineBreaks": True}, - ) - assert result == "AB\nCD" - - -def test_no_line_break_when_first_word_fits_on_previous_line() -> None: - """Should assume there is no line break if the first word can fit on the previous line.""" - result = character_codes_to_string( - [[1, 0], [2, 0]], - {"allowLineBreaks": True}, - ) - assert result == "A B" diff --git a/python/tests/test_classic.py b/python/tests/test_classic.py deleted file mode 100644 index 9980407..0000000 --- a/python/tests/test_classic.py +++ /dev/null @@ -1,201 +0,0 @@ -"""Test classic. - -Port of Vestaboard/vbml/src/__tests__/classic.spec.ts -""" - -from __future__ import annotations - -from pyvbml.classic import classic - - -def test_converts_string_to_classic_board() -> None: - """Should convert string to classic board.""" - mock_board = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 8, 5, 12, 12, 15, 55, 0, 23, 15, 18, 12, 4, 37, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - assert classic("Hello, World!") == mock_board - - -def test_converts_embedded_char_code_string() -> None: - """Should convert embedded char code string to classic board.""" - mock_board = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 8, 5, 12, 12, 15, 55, 0, 23, 15, 18, 12, 4, 37, 0, 29, 33, 0, 0, 0, 0, 0], - [0, 65, 32, 32, 65, 0, 34, 35, 65, 0, 65, 28, 36, 0, 8, 5, 12, 12, 15, 0, 0, 0], - [0, 23, 15, 18, 12, 4, 0, 23, 8, 1, 20, 19, 21, 16, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - string = "Hello, World{37} 37 {65}66{65} 89{65} {65}20 hello world whatsup" - assert classic(string, extra_h_padding=0) == mock_board - - -def test_converts_longer_string() -> None: - """Should convert longer string to classic board.""" - string = "reallylongwordthatismorethantwentytwocharcters" - assert classic(string) == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 18, 5, 1, 12, 12, 25, 12, 15, 14, 7, 23, 15, 18, 4, 20, 8, 1, 20, 0, 0, 0], - [0, 9, 19, 13, 15, 18, 5, 20, 8, 1, 14, 20, 23, 5, 14, 20, 25, 20, 23, 0, 0, 0], - [0, 15, 3, 8, 1, 18, 3, 20, 5, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_converts_ae_umlaut() -> None: - """Should convert Ƥ to ae.""" - assert classic("ƤƄ") == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_converts_long_string_with_digits() -> None: - """Should convert long string with digits to classic board.""" - assert classic("reallylongwordthatismorethan22charcters") == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 18, - 5, - 1, - 12, - 12, - 25, - 12, - 15, - 14, - 7, - 23, - 15, - 18, - 4, - 20, - 8, - 1, - 20, - 9, - 19, - 13, - 15, - ], - [18, 5, 20, 8, 1, 14, 28, 28, 3, 8, 1, 18, 3, 20, 5, 18, 19, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_converts_single_newline() -> None: - """Should convert single new line string to classic board.""" - string = "hello\n world" - assert classic(string) == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_converts_double_newline() -> None: - """Should convert double newline string to classic board.""" - assert classic("hello\n\nworld") == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_empty_string_returns_empty_board() -> None: - """Should convert string to classic board (empty string).""" - empty_row = [0] * 22 - assert classic("") == [empty_row] * 6 - - -def test_single_char_code() -> None: - """Should convert char code 1 string to classic board with `a`.""" - assert classic("{1}") == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_hyphen_string() -> None: - """Should convert hyphen string to classic board.""" - assert classic("- -hyphen") == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 44, 0, 44, 8, 25, 16, 8, 5, 14, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_special_characters() -> None: - """Should convert special character strings to classic board.""" - string = "!@#$%^&*()_+Ć„ĆŸāˆ‚Ę’Ā©Ė™āˆ†ĖšĀ¬ĀµāˆšĆ§āˆ«ĖœĀµā‰¤ā‰„Ć·{}" - assert classic(string) == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [37, 38, 39, 40, 54, 0, 47, 0, 41, 42, 46, 1, 19, 19, 0, 6, 0, 3, 0, 41, 42, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - # Note: TS expected c and {} on a second content row. In Python the empty-word - # separator logic packs them all into one row (semantically equivalent output). - - -def test_converts_emoji_colors() -> None: - """Should convert emoji colors to classic board.""" - assert classic("šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›") == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 63, 64, 65, 66, 67, 68, 69, 70, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_respects_double_spaces() -> None: - """Should respect double spaces in string.""" - assert classic("hello world", preserve_double_spaces=True) == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_preserves_triple_spaces() -> None: - """Should preserve triple spaces.""" - assert classic("hello world", preserve_double_spaces=True) == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] diff --git a/python/tests/test_copy_character_codes.py b/python/tests/test_copy_character_codes.py deleted file mode 100644 index 5921eed..0000000 --- a/python/tests/test_copy_character_codes.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Test copy character codes. - -Port of Vestaboard/vbml/src/__tests__/copyCharacterCodes.spec.ts -""" - -from __future__ import annotations - -from pyvbml.copy_character_codes import copy_character_codes - - -def test_deep_copies_character_codes() -> None: - """Should deep copy character codes.""" - characters = [[1, 2]] - result = copy_character_codes(characters) - - assert result == [[1, 2]] - assert result is not characters - - # Mutating the original must not affect the copy - characters[0][0] = 3 - assert result[0][0] == 1 diff --git a/python/tests/test_create_empty_board.py b/python/tests/test_create_empty_board.py deleted file mode 100644 index 250c305..0000000 --- a/python/tests/test_create_empty_board.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Test create empty board.""" - -from __future__ import annotations - -from pyvbml.create_empty_board import create_empty_board - - -def test_create_empty_board() -> None: - """Test create empty board.""" - result = create_empty_board(3, 2) - assert result == [[0, 0], [0, 0], [0, 0]] diff --git a/python/tests/test_has_special_characters.py b/python/tests/test_has_special_characters.py deleted file mode 100644 index 95d2431..0000000 --- a/python/tests/test_has_special_characters.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Test has special characters. - -Port of Vestaboard/vbml/src/__tests__/hasSpecialCharacters.spec.ts -""" - -from __future__ import annotations - -from pyvbml.has_special_characters import has_special_characters - - -def test_returns_true_for_special_character() -> None: - """Should return true if text contains special characters.""" - assert has_special_characters("Ƥ") is True - - -def test_returns_true_for_mixed_special_and_standard() -> None: - """Should return true if text contains special characters mixed with standard characters.""" - assert has_special_characters("Ƥa") is True - - -def test_returns_false_for_lowercase_alphabet() -> None: - """Should return false if text does not contain special characters.""" - assert has_special_characters("abcdefghijklmnopqrstuvwxyz") is False - - -def test_returns_false_for_uppercase_alphabet() -> None: - """Should return false if text does not contain special characters (uppercased).""" - assert has_special_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZ") is False - - -def test_returns_false_for_standard_numbers() -> None: - """Should return false if text is standard numbers.""" - assert has_special_characters("0123456789") is False - - -def test_returns_false_for_standard_vestaboard_symbols() -> None: - """Should return false if text is standard symbols supported by Vestaboard.""" - assert has_special_characters("!@#$()-+&=;:'\"" + "%,./?°") is False - - -def test_returns_false_for_empty_string() -> None: - """Should return false if text is empty.""" - assert has_special_characters("") is False - - -def test_excludes_newlines() -> None: - """Should exclude newlines.""" - assert has_special_characters("Hello\nWorld") is False - - -def test_excludes_ios_single_quote() -> None: - """Should exclude the single quote from the iOS keyboard.""" - assert has_special_characters("\u2019") is False - - -def test_excludes_ios_double_quote() -> None: - """Should exclude the double quote from the iOS keyboard.""" - assert has_special_characters("\u201c") is False - - -def test_excludes_white_color_swatch() -> None: - """Should exclude white color swatch.""" - assert has_special_characters("⬜") is False - - -def test_excludes_black_color_swatch() -> None: - """Should exclude black color swatch.""" - assert has_special_characters("⬛") is False - - -def test_excludes_orange_color_swatch() -> None: - """Should exclude orange color swatch.""" - assert has_special_characters("🟧") is False - - -def test_includes_fractions() -> None: - """Should include fractions.""" - assert has_special_characters("½") is True diff --git a/python/tests/test_horizontal_align.py b/python/tests/test_horizontal_align.py deleted file mode 100644 index 895237f..0000000 --- a/python/tests/test_horizontal_align.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Test horizontal align.""" - -from __future__ import annotations - -from pyvbml.horizontal_align import Justify, horizontal_align - - -def test_horizontal_align() -> None: - """Test horizontal align.""" - assert horizontal_align(5, Justify.CENTER, [[1, 0, 2]]) == [[0, 1, 0, 2, 0]] - assert horizontal_align(6, Justify.CENTER, [[1, 0, 2]]) == [[0, 1, 0, 2, 0, 0]] - - assert horizontal_align(5, Justify.LEFT, [[1, 0, 2]]) == [[1, 0, 2]] - assert horizontal_align(6, Justify.LEFT, [[1, 0, 2]]) == [[1, 0, 2]] - - assert horizontal_align(5, Justify.RIGHT, [[1, 0, 2]]) == [[0, 0, 1, 0, 2]] - assert horizontal_align(6, Justify.RIGHT, [[1, 0, 2]]) == [[0, 0, 0, 1, 0, 2]] - - assert horizontal_align(5, Justify.JUSTIFIED, [[1, 0, 2]]) == [[0, 1, 0, 2, 0]] - assert horizontal_align(6, Justify.JUSTIFIED, [[1, 0, 2]]) == [[0, 1, 0, 2, 0, 0]] diff --git a/python/tests/test_parse_component.py b/python/tests/test_parse_component.py deleted file mode 100644 index df52da4..0000000 --- a/python/tests/test_parse_component.py +++ /dev/null @@ -1,477 +0,0 @@ -"""Test parse component. - -Port of Vestaboard/vbml/src/__tests__/parseComponent.spec.ts - -Note on currying: the TS source calls parseComponent(height, width, props?)(component). -Python doesn't auto-curry, so we use a thin helper that mirrors the same call pattern. -""" - -from __future__ import annotations - -from pyvbml.parse_component import parse_absolute_component, parse_component -from pyvbml.types import Align, Justify - -# --------------------------------------------------------------------------- -# Curry-style helpers matching the TS call signatures -# --------------------------------------------------------------------------- - - -def _pc(height, width, props=None): - """parseComponent(height, width, props?)(component) → result.""" - - def _call(component): - return parse_component(height, width, props or {}, component) - - return _call - - -def _pac(height, width, props=None): - """parseAbsoluteComponent(height, width, props?)(component) → result.""" - - def _call(component): - return parse_absolute_component(height, width, props or {}, component) - - return _call - - -# --------------------------------------------------------------------------- -# Tests -# --------------------------------------------------------------------------- - - -def test_formats_plain_text() -> None: - """Should format a message with plain text.""" - result = _pc(1, 12)({"template": "Hello World!"}) - assert result == [[8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37]] - - -def test_formats_longer_plain_text() -> None: - """Should format a longer message with plain text.""" - result = _pc(2, 12)({"template": "Thank you for having us!"}) - assert result == [ - [20, 8, 1, 14, 11, 0, 25, 15, 21, 0, 0, 0], - [6, 15, 18, 0, 8, 1, 22, 9, 14, 7, 0, 0], - ] - - -def test_formats_longer_message_centered() -> None: - """Should format a longer message center with plain text.""" - result = _pc(2, 22)( - { - "template": "Thank you for having us!", - "style": {"justify": Justify.CENTER}, - } - ) - assert result == [ - [ - 0, - 20, - 8, - 1, - 14, - 11, - 0, - 25, - 15, - 21, - 0, - 6, - 15, - 18, - 0, - 8, - 1, - 22, - 9, - 14, - 7, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 19, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_adds_extra_spaces() -> None: - """Should add extra spaces.""" - result = _pc(1, 13)({"template": "Hello World!"}) - assert result == [[8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37, 0]] - - -def test_automatically_breaks_line() -> None: - """Should automatically break the line.""" - result = _pc(2, 6)({"template": "Hello World!"}) - assert result == [ - [8, 5, 12, 12, 15, 0], - [23, 15, 18, 12, 4, 37], - ] - - -def test_does_not_break_when_unnecessary() -> None: - """Should not break the line if it doesn't need to.""" - result = _pc(3, 13)({"template": "Hello World!"}) - assert result == [ - [8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4, 37, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_vertically_aligns_bottom() -> None: - """Should vertically align bottom.""" - result = _pc(4, 1)({"template": "!", "style": {"align": Align.BOTTOM}}) - assert result == [[0], [0], [0], [37]] - - -def test_vertically_aligns_center() -> None: - """Should vertically align to the center.""" - result = _pc(3, 1)({"template": "!", "style": {"align": Align.CENTER}}) - assert result == [[0], [37], [0]] - - -def test_vertically_aligns_center_multiple_rows() -> None: - """Should vertically align to the center with multiple rows.""" - result = _pc(5, 1)({"template": "!", "style": {"align": Align.CENTER}}) - assert result == [[0], [0], [37], [0], [0]] - - -def test_vertically_aligns_center_sticks_to_top_when_no_even_padding() -> None: - """Should vertically align to the center by sticking to the top if there is not even padding.""" - result = _pc(6, 1)({"template": "!", "style": {"align": Align.CENTER}}) - assert result == [[0], [0], [37], [0], [0], [0]] - - -def test_horizontally_aligns_right() -> None: - """Should horizontally align right.""" - result = _pc(1, 3)({"template": "!", "style": {"justify": Justify.RIGHT}}) - assert result == [[0, 0, 37]] - - -def test_horizontally_aligns_center() -> None: - """Should horizontally align center.""" - result = _pc(1, 3)({"template": "!", "style": {"justify": Justify.CENTER}}) - assert result == [[0, 37, 0]] - - -def test_horizontally_aligns_justified() -> None: - """Should horizontally align justified.""" - result = _pc(6, 22)( - { - "template": "Testing Testing 123", - "style": {"align": Align.CENTER, "justify": Justify.JUSTIFIED}, - } - ) - assert result == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 0, - 20, - 5, - 19, - 20, - 9, - 14, - 7, - 0, - 20, - 5, - 19, - 20, - 9, - 14, - 7, - 0, - 27, - 28, - 29, - 0, - 0, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_justified_when_full_line_covered() -> None: - """Should horizontally align justified when the full line is covered.""" - result = _pc(6, 22)( - { - "template": "Testing Testing 123456", - "style": {"align": Align.CENTER, "justify": Justify.JUSTIFIED}, - } - ) - assert result == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [ - 20, - 5, - 19, - 20, - 9, - 14, - 7, - 0, - 20, - 5, - 19, - 20, - 9, - 14, - 7, - 0, - 27, - 28, - 29, - 30, - 31, - 32, - ], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_justified_when_flowing_to_next_line() -> None: - """Should horizontally align justified when we flow to the next line.""" - result = _pc(6, 22)( - { - "template": "Testing Testing 123456789", - "style": {"align": Align.CENTER, "justify": Justify.JUSTIFIED}, - } - ) - assert result == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 20, 5, 19, 20, 9, 14, 7, 0, 20, 5, 19, 20, 9, 14, 7, 0, 0, 0, 0], - [0, 0, 0, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_justified_long_complex_message() -> None: - """Should horizontally align justified a long complex message.""" - result = _pc(6, 22)( - { - "template": "Pack my box with five dozen liquor jugs. The quick brown fox jumps over the lazy dog. How vexingly quick daft zebras jump!", - "style": {"align": Align.CENTER, "justify": Justify.JUSTIFIED}, - } - ) - assert result == [ - [16, 1, 3, 11, 0, 13, 25, 0, 2, 15, 24, 0, 23, 9, 20, 8, 0, 6, 9, 22, 5, 0], - [ - 4, - 15, - 26, - 5, - 14, - 0, - 12, - 9, - 17, - 21, - 15, - 18, - 0, - 10, - 21, - 7, - 19, - 56, - 0, - 20, - 8, - 5, - ], - [ - 17, - 21, - 9, - 3, - 11, - 0, - 2, - 18, - 15, - 23, - 14, - 0, - 6, - 15, - 24, - 0, - 10, - 21, - 13, - 16, - 19, - 0, - ], - [ - 15, - 22, - 5, - 18, - 0, - 20, - 8, - 5, - 0, - 12, - 1, - 26, - 25, - 0, - 4, - 15, - 7, - 56, - 0, - 8, - 15, - 23, - ], - [22, 5, 24, 9, 14, 7, 12, 25, 0, 17, 21, 9, 3, 11, 0, 4, 1, 6, 20, 0, 0, 0], - [26, 5, 2, 18, 1, 19, 0, 10, 21, 13, 16, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - - -def test_horizontally_and_vertically_aligned_center() -> None: - """Should horizontally and vertically align center.""" - result = _pc(3, 3)( - { - "template": "!", - "style": {"justify": Justify.CENTER, "align": Align.CENTER}, - } - ) - assert result == [[0, 0, 0], [0, 37, 0], [0, 0, 0]] - - -def test_parses_character_codes() -> None: - """Should parse character codes.""" - result = _pc(1, 3)( - { - "template": "{1}{2}{3}", - "style": {"justify": Justify.CENTER, "align": Align.CENTER}, - } - ) - assert result == [[1, 2, 3]] - - -def test_breaks_on_lines_with_character_codes() -> None: - """Should break on lines with character codes.""" - result = _pc(2, 3)( - { - "template": "{1}{2} {3}{4}", - "style": {"justify": Justify.CENTER, "align": Align.CENTER}, - } - ) - assert result == [[1, 2, 0], [3, 4, 0]] - - -def test_parses_two_digit_character_codes() -> None: - """Should parse two-digit character codes.""" - result = _pc(1, 2)( - { - "template": "{68}{69}", - "style": {"justify": Justify.CENTER, "align": Align.CENTER}, - } - ) - assert result == [[68, 69]] - - -def test_throws_for_invalid_character_codes() -> None: - """Should throw for invalid character codes.""" - raised = False - try: - _pc(1, 1)( - { - "template": "{99}", - "style": {"justify": Justify.CENTER, "align": Align.CENTER}, - } - ) - except ValueError: - raised = True - assert raised - - -def test_allows_newlines() -> None: - """Should allow newlines.""" - result = _pc(2, 2)({"template": "{1}\n{1}"}) - assert result == [[1, 0], [1, 0]] - - -def test_allows_newlines_after_spaces() -> None: - """Should allow newlines after spaces.""" - result = _pc(2, 2)({"template": "{1} \n{1}"}) - assert result == [[1, 0], [1, 0]] - - -def test_allows_newlines_before_spaces() -> None: - """Should allow newlines before spaces.""" - result = _pc(2, 2)( - {"template": "{1}\n{70}{1}", "style": {"justify": Justify.CENTER}} - ) - assert result == [[1, 0], [70, 1]] - - -def test_adds_template_props() -> None: - """Should add template props.""" - result = _pc(1, 11, {"greeting": "Hello"})({"template": "{{greeting}} World"}) - assert result == [[8, 5, 12, 12, 15, 0, 23, 15, 18, 12, 4]] - - -def test_allows_conditions() -> None: - """Should allow conditions.""" - template = "I am {{#isHappy}}Happy{{/isHappy}}{{^isHappy}}Mad{{/isHappy}}" - result_happy = _pc(1, 10, {"isHappy": True})({"template": template}) - assert result_happy == [[9, 0, 1, 13, 0, 8, 1, 16, 16, 25]] - - result_mad = _pc(1, 10, {"isHappy": False})({"template": template}) - assert result_mad == [[9, 0, 1, 13, 0, 13, 1, 4, 0, 0]] - - -def test_allows_array_iteration() -> None: - """Should allow arrays to be iterated.""" - result = _pc(1, 3, {"numbers": [1, 2, 3]})( - {"template": "{{#numbers}}{{.}}{{/numbers}}"} - ) - assert result == [[27, 28, 29]] - - -def test_splits_long_words() -> None: - """Should split long words.""" - result = _pc(2, 2)({"template": "{1}{2}{3}{4}"}) - assert result == [[1, 2], [3, 4]] - - -def test_parses_absolute_component() -> None: - """Should parse absolute component.""" - component = { - "template": "Hello World!", - "style": {"absolutePosition": {"x": 4, "y": 2}, "width": 6, "height": 2}, - } - result = _pac(3, 12)(component) - assert result == { - "characters": [ - [8, 5, 12, 12, 15, 0], - [23, 15, 18, 12, 4, 37], - ], - "x": 4, - "y": 2, - } - - -def test_parses_raw_component() -> None: - """Should parse a raw component.""" - result = _pc(3, 12)({"rawCharacters": [[1, 2], [3, 4]]}) - assert result == [[1, 2], [3, 4]] - - -def test_converts_emoji_characters_to_character_codes() -> None: - """Should convert emoji characters to character codes.""" - result = _pc(1, 8)({"template": "šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›"}) - assert result == [[63, 64, 65, 66, 67, 68, 69, 70]] diff --git a/python/tests/test_random_colors.py b/python/tests/test_random_colors.py deleted file mode 100644 index 30a50e3..0000000 --- a/python/tests/test_random_colors.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Test random colors. - -Port of Vestaboard/vbml/src/__tests__/randomColors.spec.ts -""" - -from __future__ import annotations - -import random - -from pyvbml.random_colors import COLOR_CODES, random_colors - - -def test_fills_board_with_random_colors() -> None: - """Should fill a board with random colors.""" - result = random_colors(6, 22) - - assert len(result) == 6 - assert len(result[0]) == 22 - - # Validate only colors exist in the results - assert all(c in COLOR_CODES for row in result for c in row) - - -def test_fills_board_with_selected_random_colors() -> None: - """Should fill a board with selected random colors.""" - # Take 3 random colors - colors = random.sample(COLOR_CODES, 3) - result = random_colors(6, 22, colors) - - assert len(result) == 6 - assert len(result[0]) == 22 - - # Validate only the selected colors exist in the results - assert all(c in colors for row in result for c in row) diff --git a/python/tests/test_sanitize_special_characters.py b/python/tests/test_sanitize_special_characters.py deleted file mode 100644 index 63a2631..0000000 --- a/python/tests/test_sanitize_special_characters.py +++ /dev/null @@ -1,149 +0,0 @@ -"""Test sanitize special characters. - -Port of Vestaboard/vbml/src/__tests__/sanitizeSpecialCharacters.spec.ts -""" - -from __future__ import annotations - -from pyvbml.sanitize_special_characters import sanitize_special_characters - - -def test_does_not_modify_plain_text() -> None: - """Should not modify text without special characters.""" - text = "abcdefghijklmnopqrstuvwxyz" - assert sanitize_special_characters(text) == text - - -def test_replaces_accented_character() -> None: - """Should replace special characters with their equivalent.""" - assert sanitize_special_characters("ƃ") == "a" - - -def test_handles_sentence() -> None: - """Should handle a sentence or two.""" - text = "hello world" - assert sanitize_special_characters(text) == text - - -def test_handles_mixed_special_characters() -> None: - """Should handle mixed special characters in text.""" - assert sanitize_special_characters("hĆ©llo wĆ“rld") == "hello world" - - -def test_handles_multiple_special_characters_together() -> None: - """Should handle multiple special characters together.""" - assert sanitize_special_characters("ëï") == "ei" - - -def test_replaces_fractions_with_multiple_characters() -> None: - """Should replace fractions with multiple characters.""" - assert sanitize_special_characters("½") == "1/2" - - -def test_sanitizes_variation_selector_16_from_heart_emoji() -> None: - """Should sanitize variation selector-16 (U+FE0F) from ā¤ļø.""" - assert sanitize_special_characters("ā¤ļø") == "ā¤" - - -def test_sanitizes_variation_selector_16_from_unicode_literal() -> None: - r"""Should sanitize variation selector-16 (U+FE0F) from the string literal \\u2764\\uFE0F.""" - assert sanitize_special_characters("\u2764\ufe0f") == "\u2764" - - -def test_does_not_replace_vestaboard_heart() -> None: - """Should not replace Vestaboard Note unicode hearts (U+2764).""" - assert sanitize_special_characters("\u2764") == "ā¤" - - -def test_accepts_whitespace_after_heart() -> None: - r"""Should accept whitespace after \\u2764 (U+2764).""" - text = "\u2764 " - assert sanitize_special_characters(text) == text - - -def test_preserves_spaces_between_black_hearts() -> None: - """Should not clear whitespace between black heart unicode characters.""" - test_string = "ā¤ ā¤ ā¤ ā¤ ā¤" - assert sanitize_special_characters(test_string) == test_string - - -def test_preserves_space_between_heart_and_latin_glyph() -> None: - r"""Should not trim whitespace when \\u2764 is followed by a latin glyph.""" - test_string = "\u2764 A" - assert sanitize_special_characters(test_string) == test_string - - -def test_preserves_space_between_heart_and_emoji() -> None: - r"""Should not trim whitespace when \\u2764 is followed by an emoji.""" - test_string = "\u2764 🟧" - assert sanitize_special_characters(test_string) == test_string - - -def test_converts_unsupported_sequenced_emojis_to_whitespace() -> None: - """Should convert unsupported, sequenced emojis to whitespace.""" - test_string = "ā˜ ļøāš ļøāœ…ā–¶ļøāœØāŒ›ļø" - equivalent_whitespace = "\u0020" * 6 - assert sanitize_special_characters(test_string) == equivalent_whitespace - - -def test_handles_heart_emoji_and_unsupported_emojis() -> None: - """Should handle the heart emoji and unsupported emojis.""" - test_string = "ā¤ļøā˜ ļøāš ļøāœ…ā–¶ļøāœØāŒ›ļø" - expectation = "\u2764" + "\u0020" * 6 - assert sanitize_special_characters(test_string) == expectation - - -def test_sanitizes_german_and_special_characters() -> None: - """Should sanitize all German and special characters.""" - assert sanitize_special_characters("Ƥ") == "AE" - assert sanitize_special_characters("Ƅ") == "AE" - assert sanitize_special_characters("ƶ") == "OE" - assert sanitize_special_characters("Ɩ") == "OE" - assert sanitize_special_characters("ü") == "UE" - assert sanitize_special_characters("Ü") == "UE" - assert sanitize_special_characters("ß") == "SS" - - assert sanitize_special_characters("Ćø") == "o" - assert sanitize_special_characters("Ć„") == "a" - - assert sanitize_special_characters("œ") == "OE" - assert sanitize_special_characters("Ʀ") == "AE" - - assert sanitize_special_characters("Ƨ") == "c" - assert sanitize_special_characters("ʒ") == "f" - assert sanitize_special_characters("µ") == " " # micro sign → space - - assert sanitize_special_characters("…") == "..." - assert sanitize_special_characters("–") == "-" - assert sanitize_special_characters("⁄") == "/" - - all_chars = "Ć¤Ć„Ć¶Ć–Ć¼ĆœĆŸĆøĆ„Å“Ć¦Ć§Ę’Āµā€¦ā€“ā„āˆ‘Ā”Ā¶Ā¢[]|{}ā‰ Āæā‚¬Ā®ā€ ĀØĻ€ā€¢Ā±āˆ‚Ā©Āŗāˆ†@Ā„ā‰ˆāˆšāˆ«~āˆž" - result = sanitize_special_characters(all_chars) - assert result - assert isinstance(result, str) - assert not any(c in result for c in "Ć¤Ć„Ć¶Ć–Ć¼ĆœĆŸĆøĆ„Å“Ć¦Ć§Ę’Āµ") - - -def test_german_text_with_umlauts_in_context() -> None: - """Should handle German text with umlauts in context.""" - german_text = "Über die Brücke gehen wir für Ɩsterreich" - result = sanitize_special_characters(german_text) - assert result == "UEber die BrUEcke gehen wir fUEr OEsterreich" - - -def test_german_sharp_s_in_context() -> None: - """Should handle German sharp s (ß) in context.""" - assert sanitize_special_characters("Straße") == "StraSSe" - - -def test_converts_sharp_s_in_all_contexts() -> None: - """Should convert ß to SS in all contexts.""" - cases = [ - ("ß", "SS"), - ("Straße", "StraSSe"), - ("fußball", "fuSSball"), - ("groß", "groSS"), - ("weiß", "weiSS"), - ] - for text, expected in cases: - assert sanitize_special_characters(text) == expected diff --git a/python/tests/test_vbml.py b/python/tests/test_vbml.py deleted file mode 100644 index d619fe1..0000000 --- a/python/tests/test_vbml.py +++ /dev/null @@ -1,806 +0,0 @@ -"""Test VBML. - -Port from Vestaboard/vbml/src/__tests__/vbml.spec.ts -""" - -from __future__ import annotations - -from pyvbml import Align, Justify, vbml - - -def test_parses_single_component() -> None: - """Should parse a single component on a board.""" - result = vbml.parse( - { - "style": {"height": 1, "width": 2}, - "components": [{"template": "hi"}], - } - ) - assert result == [[8, 9]] - - -def test_layouts_components_side_by_side() -> None: - """Should layout components side by side.""" - result = vbml.parse( - { - "style": {"height": 1, "width": 4}, - "components": [ - {"template": "hi", "style": {"width": 2, "height": 1}}, - {"template": "hi", "style": {"width": 2, "height": 1}}, - ], - } - ) - assert result == [[8, 9, 8, 9]] - - -def test_formats_ae_umlaut() -> None: - """Should format ƤƄ to aeae.""" - result = vbml.parse( - { - "style": {"height": 1, "width": 4}, - "components": [{"template": "ƤƄ", "style": {"width": 4, "height": 1}}], - } - ) - assert result == [[1, 5, 1, 5]] - - -def test_layouts_components_vertically() -> None: - """Should layout components vertically.""" - result = vbml.parse( - { - "style": {"height": 2, "width": 2}, - "components": [ - {"template": "hi", "style": {"width": 2, "height": 1}}, - {"template": "hi", "style": {"width": 2, "height": 1}}, - ], - } - ) - assert result == [[8, 9], [8, 9]] - - -def test_flows_third_component_to_next_line() -> None: - """Should flow a third component to the next line.""" - result = vbml.parse( - { - "style": {"height": 2, "width": 4}, - "components": [ - {"template": "{1}{2}", "style": {"width": 2, "height": 1}}, - {"template": "{3}{4}", "style": {"width": 2, "height": 1}}, - {"template": "{5}{6}", "style": {"width": 2, "height": 1}}, - ], - } - ) - assert result == [[1, 2, 3, 4], [5, 6, 0, 0]] - - -def test_justifies_content_vertically() -> None: - """Should justify the content vertically.""" - result = vbml.parse( - { - "style": {"height": 5, "width": 1}, - "components": [ - { - "template": "abcd", - "style": {"height": 5, "width": 1, "align": Align.JUSTIFIED}, - } - ], - } - ) - assert result == [[0], [1], [2], [3], [4]] - - -def test_justifies_content_vertically_three_chars() -> None: - """Should justify the content vertically with three characters and rows.""" - result = vbml.parse( - { - "style": {"height": 5, "width": 1}, - "components": [ - { - "template": "abc", - "style": {"height": 5, "width": 1, "align": Align.JUSTIFIED}, - } - ], - } - ) - assert result == [[0], [1], [2], [3], [0]] - - -def test_layouts_absolute_components_by_relative() -> None: - """Should layout absolute components by relative components.""" - result = vbml.parse( - { - "style": {"height": 22, "width": 6}, - "components": [ - { - "template": "abc", - "style": { - "height": 6, - "width": 22, - "align": Align.TOP, - "justify": Justify.LEFT, - }, - }, - { - "template": "def", - "style": { - "height": 1, - "width": 3, - "align": Align.TOP, - "justify": Justify.LEFT, - "absolutePosition": {"x": 3, "y": 0}, - }, - }, - ], - } - ) - # Board is 22 rows Ɨ 6 cols; TS test expected 22-element rows which implies - # height/width were swapped in the original test. Our board is correctly 6 wide. - assert result[0] == [1, 2, 3, 4, 5, 6] - - -def test_layouts_absolute_over_relative_components() -> None: - """Should layout absolute components over relative components.""" - result = vbml.parse( - { - "style": {"height": 22, "width": 6}, - "components": [ - { - "template": "abc", - "style": { - "height": 6, - "width": 22, - "align": Align.TOP, - "justify": Justify.LEFT, - }, - }, - { - "template": "def", - "style": { - "height": 1, - "width": 3, - "align": Align.TOP, - "justify": Justify.LEFT, - "absolutePosition": {"x": 0, "y": 0}, - }, - }, - ], - } - ) - # Board is 22 rows Ɨ 6 cols; absolute component (def=4,5,6) overwrites from x=0. - assert result[0] == [4, 5, 6, 0, 0, 0] - - -def test_layouts_absolute_over_relative_components_standard_size() -> None: - """Should layout absolute components over relative components (standard board size).""" - result = vbml.parse( - { - "style": {"height": 6, "width": 22}, - "components": [ - { - "template": "abc", - "style": { - "height": 6, - "width": 22, - "align": Align.TOP, - "justify": Justify.LEFT, - }, - }, - { - "template": "def", - "style": { - "height": 1, - "width": 3, - "align": Align.TOP, - "justify": Justify.LEFT, - "absolutePosition": {"x": 0, "y": 0}, - }, - }, - ], - } - ) - expected = [4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - assert result[0] == expected - - -def test_layouts_raw_components() -> None: - """Should layout raw components.""" - result = vbml.parse( - { - "style": {"height": 6, "width": 22}, - "components": [{"rawCharacters": [[1, 2, 3]]}], - } - ) - expected = [1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - assert result[0] == expected - - -def test_mountain_background_clock() -> None: - """Should layout absolute components with raw components for a mountain background clock.""" - result = vbml.parse( - { - "props": {"time": "12:00 PM"}, - "style": {"height": 6, "width": 22}, - "components": [ - { - "rawCharacters": [ - [ - 68, - 68, - 68, - 68, - 68, - 69, - 69, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - ], - [ - 68, - 68, - 68, - 68, - 69, - 69, - 69, - 69, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 65, - 65, - 65, - 65, - 68, - 68, - ], - [ - 63, - 63, - 63, - 69, - 66, - 69, - 66, - 69, - 69, - 63, - 63, - 63, - 63, - 63, - 63, - 65, - 65, - 65, - 65, - 65, - 65, - 63, - ], - [ - 63, - 63, - 66, - 66, - 66, - 69, - 66, - 66, - 66, - 66, - 63, - 63, - 63, - 63, - 63, - 65, - 65, - 65, - 65, - 65, - 65, - 63, - ], - [ - 64, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 64, - 64, - 64, - 64, - 64, - 65, - 65, - 65, - 65, - 64, - 64, - ], - [ - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 64, - 64, - 64, - 64, - 64, - 64, - 64, - 64, - 64, - 64, - ], - ] - }, - { - "template": "{{time}}", - "style": { - "height": 1, - "width": 8, - "absolutePosition": {"x": 11, "y": 3}, - }, - }, - ], - } - ) - assert result == [ - [ - 68, - 68, - 68, - 68, - 68, - 69, - 69, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - ], - [ - 68, - 68, - 68, - 68, - 69, - 69, - 69, - 69, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 68, - 65, - 65, - 65, - 65, - 68, - 68, - ], - [ - 63, - 63, - 63, - 69, - 66, - 69, - 66, - 69, - 69, - 63, - 63, - 63, - 63, - 63, - 63, - 65, - 65, - 65, - 65, - 65, - 65, - 63, - ], - [ - 63, - 63, - 66, - 66, - 66, - 69, - 66, - 66, - 66, - 66, - 63, - 27, - 28, - 50, - 36, - 36, - 0, - 16, - 13, - 65, - 65, - 63, - ], - [ - 64, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 64, - 64, - 64, - 64, - 64, - 65, - 65, - 65, - 65, - 64, - 64, - ], - [ - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 66, - 64, - 64, - 64, - 64, - 64, - 64, - 64, - 64, - 64, - 64, - ], - ] - - -def test_calendar_component_christmas() -> None: - """Should layout a calendar component for Christmas šŸŽ„.""" - result = vbml.parse( - { - "style": {"height": 6, "width": 22}, - "components": [ - { - "calendar": { - "defaultDayColor": 66, - "month": 12, - "year": 2024, - "days": {"25": 63}, - }, - "style": {"absolutePosition": {"x": 0, "y": 0}}, - } - ], - } - ) - assert result == [ - [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 33, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 34, 44, 27, 30, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 31, 44, 28, 27, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 28, 44, 28, 34, 66, 66, 66, 63, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 35, 44, 29, 27, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - for row in result: - assert len(row) == 22 - - -def test_minimalist_calendar() -> None: - """Should layout a minimalist calendar component.""" - result = vbml.parse( - { - "style": {"height": 6, "width": 22}, - "components": [ - { - "style": {"absolutePosition": {"x": 0, "y": 0}}, - "calendar": { - "defaultDayColor": 66, - "hideDates": True, - "hideMonthYear": True, - "hideSMTWTFS": True, - "month": 12, - "year": 2024, - "days": {"25": 63}, - }, - } - ], - } - ) - assert result == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 63, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - for row in result: - assert len(row) == 22 - - -def test_calendar_with_other_components() -> None: - """Should layout a calendar component with other components.""" - result = vbml.parse( - { - "style": {"height": 6, "width": 22}, - "components": [ - { - "template": "December 2024 Calendar", - "style": { - "height": 6, - "width": 10, - "absolutePosition": {"x": 13, "y": 0}, - }, - }, - { - "calendar": { - "month": 12, - "year": 2024, - "days": { - "1": 63, - "2": 64, - "3": 65, - "4": 66, - "5": 67, - "6": 68, - "7": 63, - }, - }, - "style": {"absolutePosition": {"x": 0, "y": 0}}, - }, - ], - } - ) - assert result == [ - [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 4, 5, 3, 5, 13, 2, 5, 18, 0], - [ - 0, - 27, - 44, - 33, - 0, - 63, - 64, - 65, - 66, - 67, - 68, - 63, - 0, - 28, - 36, - 28, - 30, - 0, - 0, - 0, - 0, - 0, - ], - [ - 0, - 34, - 44, - 27, - 30, - 65, - 65, - 65, - 65, - 65, - 65, - 65, - 0, - 3, - 1, - 12, - 5, - 14, - 4, - 1, - 18, - 0, - ], - [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 35, 44, 29, 27, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] - for row in result: - assert len(row) == 22 - - -def test_calendar_on_the_right() -> None: - """Should layout a calendar component on the right.""" - result = vbml.parse( - { - "style": {"height": 6, "width": 22}, - "components": [ - { - "template": "Merry Christmas", - "style": { - "height": 6, - "width": 10, - "absolutePosition": {"x": 0, "y": 0}, - }, - }, - { - "calendar": { - "defaultDayColor": 66, - "month": 12, - "year": 2028, - "days": {"25": 63}, - }, - "style": {"absolutePosition": {"x": 10, "y": 0}}, - }, - ], - } - ) - assert result == [ - [ - 13, - 5, - 18, - 18, - 25, - 0, - 0, - 0, - 0, - 0, - 27, - 28, - 59, - 28, - 34, - 19, - 13, - 20, - 23, - 20, - 6, - 19, - ], - [3, 8, 18, 9, 19, 20, 13, 1, 19, 0, 0, 27, 44, 28, 0, 0, 0, 0, 0, 0, 66, 66], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 44, 35, 0, 66, 66, 66, 66, 66, 66, 66], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 36, 44, 27, 32, 66, 66, 66, 66, 66, 66, 66], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 33, 44, 28, 29, 66, 66, 66, 66, 66, 66, 66], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 30, 44, 29, 27, 66, 63, 66, 66, 66, 66, 66], - ] - for row in result: - assert len(row) == 22 - - -def test_respects_double_returns() -> None: - """Should respect double returns.""" - result = vbml.parse( - { - "style": {"height": 3, "width": 2}, - "components": [ - { - "template": "h\n\ni", - "style": {"align": Align.TOP, "justify": Justify.LEFT}, - } - ], - } - ) - assert result == [[8, 0], [0, 0], [9, 0]] - - -def test_respects_triple_returns() -> None: - """Should respect triple returns.""" - result = vbml.parse( - { - "style": {"height": 4, "width": 2}, - "components": [ - { - "template": "h\n\n\ni", - "style": {"align": Align.TOP, "justify": Justify.LEFT}, - } - ], - } - ) - assert result == [[8, 0], [0, 0], [0, 0], [9, 0]] - - -def test_random_colors() -> None: - """Should let us use random colors.""" - result = vbml.parse( - { - "style": {"height": 1, "width": 1}, - "components": [{"randomColors": {"colors": [61]}}], - } - ) - assert result[0][0] == 61 - - -def test_vestaboard_note() -> None: - """Test Vestaboard Note.""" - result = vbml.parse( - { - "style": {"height": 3, "width": 15}, - "components": [ - { - "style": {"justify": Justify.CENTER, "align": Align.CENTER}, - "template": "Iā¤ļøU", - } - ], - } - ) - assert result == [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 9, 62, 21, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ] diff --git a/python/tests/test_vertical_align.py b/python/tests/test_vertical_align.py deleted file mode 100644 index 5602a4a..0000000 --- a/python/tests/test_vertical_align.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Test vertical align.""" - -from __future__ import annotations - -from pyvbml.vertical_align import Align, vertical_align - - -def test_vertical_align() -> None: - """Test vertical align.""" - assert vertical_align(3, Align.TOP, [[1, 2]]) == [[1, 2]] - assert vertical_align(3, Align.BOTTOM, [[1, 2]]) == [[], [], [1, 2]] - assert vertical_align(3, Align.JUSTIFIED, [[1, 2]]) == [[], [1, 2], []] - assert vertical_align(4, Align.JUSTIFIED, [[1, 2]]) == [[], [], [1, 2], []] - assert vertical_align(3, Align.CENTER, [[1, 2]]) == [[], [1, 2], []] - assert vertical_align(4, Align.CENTER, [[1, 2]]) == [[], [1, 2], [], []] From 32420f3eb5066da3358a1638a2c47186a75a0527 Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Wed, 18 Mar 2026 22:48:51 -0700 Subject: [PATCH 05/12] test(conformance): migrate php suites to shared fixtures --- php/tests/CalendarTest.php | 60 ---- php/tests/CharacterCodesToAsciiTest.php | 27 -- php/tests/CharacterCodesToStringTest.php | 55 --- php/tests/ClassicTest.php | 189 ---------- .../Conformance/CalendarConformanceTest.php | 30 ++ .../CharacterCodesToAsciiConformanceTest.php | 25 ++ .../CharacterCodesToStringConformanceTest.php | 25 ++ .../Conformance/ClassicConformanceTest.php | 22 ++ php/tests/Conformance/ConformanceTestCase.php | 159 +++++++++ .../HasSpecialCharactersConformanceTest.php | 22 ++ .../ParseComponentConformanceTest.php | 35 ++ ...nitizeSpecialCharactersConformanceTest.php | 22 ++ php/tests/Conformance/VbmlConformanceTest.php | 22 ++ php/tests/HasSpecialCharactersTest.php | 79 ----- php/tests/SanitizeSpecialCharactersTest.php | 151 -------- php/tests/VbmlTest.php | 329 ------------------ 16 files changed, 362 insertions(+), 890 deletions(-) delete mode 100644 php/tests/CalendarTest.php delete mode 100644 php/tests/CharacterCodesToAsciiTest.php delete mode 100644 php/tests/CharacterCodesToStringTest.php delete mode 100644 php/tests/ClassicTest.php create mode 100644 php/tests/Conformance/CalendarConformanceTest.php create mode 100644 php/tests/Conformance/CharacterCodesToAsciiConformanceTest.php create mode 100644 php/tests/Conformance/CharacterCodesToStringConformanceTest.php create mode 100644 php/tests/Conformance/ClassicConformanceTest.php create mode 100644 php/tests/Conformance/ConformanceTestCase.php create mode 100644 php/tests/Conformance/HasSpecialCharactersConformanceTest.php create mode 100644 php/tests/Conformance/ParseComponentConformanceTest.php create mode 100644 php/tests/Conformance/SanitizeSpecialCharactersConformanceTest.php create mode 100644 php/tests/Conformance/VbmlConformanceTest.php delete mode 100644 php/tests/HasSpecialCharactersTest.php delete mode 100644 php/tests/SanitizeSpecialCharactersTest.php delete mode 100644 php/tests/VbmlTest.php diff --git a/php/tests/CalendarTest.php b/php/tests/CalendarTest.php deleted file mode 100644 index 56d95bd..0000000 --- a/php/tests/CalendarTest.php +++ /dev/null @@ -1,60 +0,0 @@ -assertEquals([ - [28, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], $result); - } - - public function testShouldRenderSingleDateHeaderWhenLastWeekHas1Day(): void - { - $result = Calendar::makeCalendar( - '2', - '2027', - [] - ); - $this->assertEquals([ - [28, 59, 28, 33, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], $result); - } - - public function testShouldNotHighlightDaysOutsideOfMonth(): void - { - $result = Calendar::makeCalendar( - '6', - '2026', - ["30" => 67, "31" => 67, "32" => 67] - ); - $this->assertEquals([ - [32, 59, 28, 32, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 32, 0, 0, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 33, 44, 27, 29, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 30, 44, 28, 36, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 27, 44, 28, 33, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 34, 44, 29, 36, 65, 65, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], $result); - } -} diff --git a/php/tests/CharacterCodesToAsciiTest.php b/php/tests/CharacterCodesToAsciiTest.php deleted file mode 100644 index 9289dbb..0000000 --- a/php/tests/CharacterCodesToAsciiTest.php +++ /dev/null @@ -1,27 +0,0 @@ -assertEquals('šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›', $result); - } - - public function testShouldHandleRows(): void - { - $result = CharacterCodesToAscii::convert([[63, 64], [63, 64]]); - $this->assertEquals("🟄🟧\n\n🟄🟧", $result); - } - - public function testShouldSpaceOutLetters(): void - { - $result = CharacterCodesToAscii::convert([[1, 2]]); - $this->assertEquals('A B ', $result); - } -} diff --git a/php/tests/CharacterCodesToStringTest.php b/php/tests/CharacterCodesToStringTest.php deleted file mode 100644 index d03addd..0000000 --- a/php/tests/CharacterCodesToStringTest.php +++ /dev/null @@ -1,55 +0,0 @@ -assertEquals('AB', $result); - } - - public function testShouldConvertTwoLineSentence(): void - { - $result = CharacterCodesToString::convert([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [20, 8, 9, 19, 0, 9, 19, 0, 1, 0, 12, 15, 14, 7, 5, 18, 0, 2, 12, 15, 3, 11], - [20, 8, 1, 20, 0, 19, 16, 1, 14, 19, 0, 28, 0, 12, 9, 14, 5, 19, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - $this->assertEquals('THIS IS A LONGER BLOCK THAT SPANS 2 LINES', $result); - } - - public function testShouldHandleBreaks(): void - { - $result = CharacterCodesToString::convert([ - [0, 0, 8, 1, 14, 4, 12, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 2, 18, 5, 1, 11, 19, 0, 7, 18, 1, 3, 5, 6, 21, 12, 12, 25, 0, 0, 0], - ]); - $this->assertEquals('HANDLE BREAKS GRACEFULLY', $result); - } - - public function testShouldHandleLineBreaks(): void - { - $result = CharacterCodesToString::convert( - [[1, 2, 0, 0, 0], [3, 4, 0, 0, 0]], - ['allowLineBreaks' => true] - ); - $this->assertEquals("AB\nCD", $result); - } - - public function testShouldAssumeNoLineBreakIfFirstWordFits(): void - { - $result = CharacterCodesToString::convert( - [[1, 0], [2, 0]], - ['allowLineBreaks' => true] - ); - $this->assertEquals('A B', $result); - } -} diff --git a/php/tests/ClassicTest.php b/php/tests/ClassicTest.php deleted file mode 100644 index aff3bb3..0000000 --- a/php/tests/ClassicTest.php +++ /dev/null @@ -1,189 +0,0 @@ -assertEquals($mockBoard, Classic::classic('Hello, World!')); - } - - public function testShouldConvertEmbeddedCharCodeString(): void - { - $mockBoard = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 8, 5, 12, 12, 15, 55, 0, 23, 15, 18, 12, 4, 37, 0, 29, 33, 0, 0, 0, 0, 0], - [0, 65, 32, 32, 65, 0, 34, 35, 65, 0, 65, 28, 36, 0, 8, 5, 12, 12, 15, 0, 0, 0], - [0, 23, 15, 18, 12, 4, 0, 23, 8, 1, 20, 19, 21, 16, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]; - $string = 'Hello, World{37} 37 {65}66{65} 89{65} {65}20 hello world whatsup'; - $this->assertEquals($mockBoard, Classic::classic($string, ['extraHPadding' => 0])); - } - - public function testShouldConvertLongerString(): void - { - $string = 'reallylongwordthatismorethantwentytwocharcters'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 18, 5, 1, 12, 12, 25, 12, 15, 14, 7, 23, 15, 18, 4, 20, 8, 1, 20, 0, 0, 0], - [0, 9, 19, 13, 15, 18, 5, 20, 8, 1, 14, 20, 23, 5, 14, 20, 25, 20, 23, 0, 0, 0], - [0, 15, 3, 8, 1, 18, 3, 20, 5, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldConvertAeToClassicBoard(): void - { - $string = 'ƤƄ'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldConvertLongStringWithDigits(): void - { - $string = 'reallylongwordthatismorethan22charcters'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [18, 5, 1, 12, 12, 25, 12, 15, 14, 7, 23, 15, 18, 4, 20, 8, 1, 20, 9, 19, 13, 15], - [18, 5, 20, 8, 1, 14, 28, 28, 3, 8, 1, 18, 3, 20, 5, 18, 19, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldConvertSingleNewlineString(): void - { - $string = "hello\n world"; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldConvertDoubleNewlineString(): void - { - $string = "hello\n\nworld"; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldConvertEmptyString(): void - { - $string = ''; - $emptyRow = array_fill(0, 22, 0); - $this->assertEquals( - array_fill(0, 6, $emptyRow), - Classic::classic($string) - ); - } - - public function testShouldConvertCharCode1(): void - { - $string = '{1}'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldConvertHyphenString(): void - { - $string = '- -hyphen'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 44, 0, 44, 8, 25, 16, 8, 5, 14, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldConvertSpecialCharacterStrings(): void - { - $string = '!@#$%^&*()_+Ć„ĆŸāˆ‚Ę’Ā©Ė™āˆ†ĖšĀ¬ĀµāˆšĆ§āˆ«ĖœĀµā‰¤ā‰„Ć·{}'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [37, 38, 39, 40, 54, 0, 47, 0, 41, 42, 46, 1, 19, 19, 0, 6, 0, 0, 0, 0, 0, 0], - [0, 3, 0, 0, 0, 0, 0, 0, 41, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldConvertEmojiColors(): void - { - $string = 'šŸŸ„šŸŸ§šŸŸØšŸŸ©šŸŸ¦šŸŸŖā¬œā¬›'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 63, 64, 65, 66, 67, 68, 69, 70, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string)); - } - - public function testShouldRespectDoubleSpaces(): void - { - $string = 'hello world'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string, ['preserveDoubleSpaces' => true])); - } - - public function testShouldPreserveTripleSpaces(): void - { - $string = 'hello world'; - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 8, 5, 12, 12, 15, 0, 0, 0, 23, 15, 18, 12, 4, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], Classic::classic($string, ['preserveDoubleSpaces' => true])); - } -} diff --git a/php/tests/Conformance/CalendarConformanceTest.php b/php/tests/Conformance/CalendarConformanceTest.php new file mode 100644 index 0000000..5950611 --- /dev/null +++ b/php/tests/Conformance/CalendarConformanceTest.php @@ -0,0 +1,30 @@ +assertCase($case, function (array $input) { + return Calendar::makeCalendar( + (string)$input['month'], + (string)$input['year'], + $input['days'] ?? [], + $input['defaultDayColor'] ?? null, + $input['hideSMTWTFS'] ?? false, + $input['hideDates'] ?? false, + $input['hideMonthYear'] ?? false + ); + }); + } +} diff --git a/php/tests/Conformance/CharacterCodesToAsciiConformanceTest.php b/php/tests/Conformance/CharacterCodesToAsciiConformanceTest.php new file mode 100644 index 0000000..032f8c7 --- /dev/null +++ b/php/tests/Conformance/CharacterCodesToAsciiConformanceTest.php @@ -0,0 +1,25 @@ +assertCase($case, function (array $input) { + return CharacterCodesToAscii::convert( + $input['characterCodes'], + $input['isWhite'] ?? false + ); + }); + } +} diff --git a/php/tests/Conformance/CharacterCodesToStringConformanceTest.php b/php/tests/Conformance/CharacterCodesToStringConformanceTest.php new file mode 100644 index 0000000..8e9117e --- /dev/null +++ b/php/tests/Conformance/CharacterCodesToStringConformanceTest.php @@ -0,0 +1,25 @@ +assertCase($case, function (array $input) { + return CharacterCodesToString::convert( + $input['characters'], + $input['options'] ?? [] + ); + }); + } +} diff --git a/php/tests/Conformance/ClassicConformanceTest.php b/php/tests/Conformance/ClassicConformanceTest.php new file mode 100644 index 0000000..96f20aa --- /dev/null +++ b/php/tests/Conformance/ClassicConformanceTest.php @@ -0,0 +1,22 @@ +assertCase($case, function (array $input) { + return Classic::classic($input['text'], $input['options'] ?? []); + }); + } +} diff --git a/php/tests/Conformance/ConformanceTestCase.php b/php/tests/Conformance/ConformanceTestCase.php new file mode 100644 index 0000000..ae9adf2 --- /dev/null +++ b/php/tests/Conformance/ConformanceTestCase.php @@ -0,0 +1,159 @@ + $caseId, + 'input' => self::readJsonFile($inputPath), + 'expected' => $expected, + ]]; + } + + return $cases; + } + + protected function assertCase(array $case, callable $run): void + { + $expected = $case['expected']; + + if (isset($expected['skip'])) { + $this->markTestSkipped($expected['skip']); + } + + if (isset($expected['error'])) { + try { + $run($case['input']); + $this->fail( + sprintf('Expected %s to throw an exception.', $case['id']) + ); + } catch (\Throwable $error) { + $this->assertSame( + $expected['error']['message'], + $error->getMessage() + ); + } + + return; + } + + $this->assertSame($expected['result'], $run($case['input'])); + } + + private static function repoRoot(): string + { + return dirname(__DIR__, 3); + } + + private static function readJsonFile(string $filePath): array + { + return json_decode( + file_get_contents($filePath), + true, + 512, + JSON_THROW_ON_ERROR + ); + } + + private static function walkJsonFiles(string $rootDir): array + { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($rootDir, RecursiveDirectoryIterator::SKIP_DOTS) + ); + + $paths = []; + foreach ($iterator as $file) { + if ($file->isFile() && str_ends_with($file->getFilename(), '.json')) { + $paths[] = $file->getPathname(); + } + } + + sort($paths); + + return $paths; + } + + private static function resolveExpected( + string $suite, + string $caseId, + array $payload, + string $platform + ): array { + $exceptionPath = self::repoRoot() + . "/test/platform-exceptions/{$platform}/{$suite}/{$caseId}.json"; + + if (is_file($exceptionPath)) { + $exception = self::readJsonFile($exceptionPath); + $reason = trim((string)($exception['reason'] ?? '')); + if ($reason === '') { + throw new \RuntimeException( + "Platform exception \"{$suite}/{$caseId}\" is missing a reason." + ); + } + + if (array_key_exists('skip', $exception)) { + if ( + array_key_exists('result', $exception) + || array_key_exists('error', $exception) + ) { + throw new \RuntimeException( + "Platform exception \"{$suite}/{$caseId}\" cannot define skip with result or error." + ); + } + + return [ + 'skip' => $reason, + ]; + } + + $hasResult = array_key_exists('result', $exception); + $hasError = array_key_exists('error', $exception); + if ($hasResult === $hasError) { + throw new \RuntimeException( + "Platform exception \"{$suite}/{$caseId}\" must define exactly one of result or error." + ); + } + + return $exception; + } + + $hasResult = array_key_exists('result', $payload); + $hasError = array_key_exists('error', $payload); + if ($hasResult === $hasError) { + throw new \RuntimeException( + "Conformance case \"{$suite}/{$caseId}\" must define exactly one of result or error." + ); + } + + return $payload; + } +} diff --git a/php/tests/Conformance/HasSpecialCharactersConformanceTest.php b/php/tests/Conformance/HasSpecialCharactersConformanceTest.php new file mode 100644 index 0000000..71c3529 --- /dev/null +++ b/php/tests/Conformance/HasSpecialCharactersConformanceTest.php @@ -0,0 +1,22 @@ +assertCase($case, function (array $input) { + return HasSpecialCharacters::check($input['text']); + }); + } +} diff --git a/php/tests/Conformance/ParseComponentConformanceTest.php b/php/tests/Conformance/ParseComponentConformanceTest.php new file mode 100644 index 0000000..77e1b01 --- /dev/null +++ b/php/tests/Conformance/ParseComponentConformanceTest.php @@ -0,0 +1,35 @@ +assertCase($case, function (array $input) { + $props = $input['props'] ?? []; + $runner = ($input['mode'] ?? 'component') === 'absolute' + ? ParseComponent::parseAbsoluteComponent( + $input['height'], + $input['width'], + $props + ) + : ParseComponent::parseComponent( + $input['height'], + $input['width'], + $props + ); + + return $runner($input['component']); + }); + } +} diff --git a/php/tests/Conformance/SanitizeSpecialCharactersConformanceTest.php b/php/tests/Conformance/SanitizeSpecialCharactersConformanceTest.php new file mode 100644 index 0000000..a179d96 --- /dev/null +++ b/php/tests/Conformance/SanitizeSpecialCharactersConformanceTest.php @@ -0,0 +1,22 @@ +assertCase($case, function (array $input) { + return SanitizeSpecialCharacters::sanitize($input['text']); + }); + } +} diff --git a/php/tests/Conformance/VbmlConformanceTest.php b/php/tests/Conformance/VbmlConformanceTest.php new file mode 100644 index 0000000..3d1519e --- /dev/null +++ b/php/tests/Conformance/VbmlConformanceTest.php @@ -0,0 +1,22 @@ +assertCase($case, function (array $input) { + return Vbml::parse($input); + }); + } +} diff --git a/php/tests/HasSpecialCharactersTest.php b/php/tests/HasSpecialCharactersTest.php deleted file mode 100644 index 1fb6f38..0000000 --- a/php/tests/HasSpecialCharactersTest.php +++ /dev/null @@ -1,79 +0,0 @@ -assertTrue(HasSpecialCharacters::check('Ƥ')); - } - - public function testShouldReturnTrueIfMixed(): void - { - $this->assertTrue(HasSpecialCharacters::check('Ƥa')); - } - - public function testShouldReturnFalseForLowercase(): void - { - $this->assertFalse(HasSpecialCharacters::check('abcdefghijklmnopqrstuvwxyz')); - } - - public function testShouldReturnFalseForUppercase(): void - { - $this->assertFalse(HasSpecialCharacters::check('ABCDEFGHIJKLMNOPQRSTUVWXYZ')); - } - - public function testShouldReturnFalseForNumbers(): void - { - $this->assertFalse(HasSpecialCharacters::check('0123456789')); - } - - public function testShouldReturnFalseForStandardSymbols(): void - { - $this->assertFalse(HasSpecialCharacters::check("!@#\$()-+&=;:'\"%,./?°")); - } - - public function testShouldReturnFalseForEmpty(): void - { - $this->assertFalse(HasSpecialCharacters::check('')); - } - - public function testShouldExcludeNewlines(): void - { - $this->assertFalse(HasSpecialCharacters::check("Hello\nWorld")); - } - - public function testShouldExcludeSingleQuoteFromIOS(): void - { - $this->assertFalse(HasSpecialCharacters::check("\u{2018}")); - } - - public function testShouldExcludeDoubleQuoteFromIOS(): void - { - $this->assertFalse(HasSpecialCharacters::check("\u{201C}")); - } - - public function testShouldExcludeWhiteColorSwatch(): void - { - $this->assertFalse(HasSpecialCharacters::check('⬜')); - } - - public function testShouldExcludeBlackColorSwatch(): void - { - $this->assertFalse(HasSpecialCharacters::check('⬛')); - } - - public function testShouldExcludeOrangeColorSwatch(): void - { - $this->assertFalse(HasSpecialCharacters::check('🟧')); - } - - public function testShouldIncludeFractions(): void - { - $this->assertTrue(HasSpecialCharacters::check('½')); - } -} diff --git a/php/tests/SanitizeSpecialCharactersTest.php b/php/tests/SanitizeSpecialCharactersTest.php deleted file mode 100644 index 23b9ab9..0000000 --- a/php/tests/SanitizeSpecialCharactersTest.php +++ /dev/null @@ -1,151 +0,0 @@ -assertEquals($text, SanitizeSpecialCharacters::sanitize($text)); - } - - public function testShouldReplaceSpecialCharacters(): void - { - $this->assertEquals('a', SanitizeSpecialCharacters::sanitize('ƃ')); - } - - public function testShouldHandleSentence(): void - { - $this->assertEquals('hello world', SanitizeSpecialCharacters::sanitize('hello world')); - } - - public function testShouldHandleMixedSpecialCharacters(): void - { - $this->assertEquals('hello world', SanitizeSpecialCharacters::sanitize('hĆ©llo wĆ“rld')); - } - - public function testShouldHandleMultipleSpecialCharactersTogether(): void - { - $this->assertEquals('ei', SanitizeSpecialCharacters::sanitize('ëï')); - } - - public function testShouldReplaceFractionsWithMultipleCharacters(): void - { - $this->assertEquals('1/2', SanitizeSpecialCharacters::sanitize('½')); - } - - public function testShouldSanitizeVariationSelectorFromHeartEmoji(): void - { - $text = "ā¤ļø"; // U+2764 U+FE0F - $this->assertEquals('ā¤', SanitizeSpecialCharacters::sanitize($text)); - } - - public function testShouldSanitizeVariationSelectorFromLiteral(): void - { - $text = "\u{2764}\u{FE0F}"; - $this->assertEquals("\u{2764}", SanitizeSpecialCharacters::sanitize($text)); - } - - public function testShouldNotReplaceVestaboardHeart(): void - { - $text = "\u{2764}"; - $this->assertEquals('ā¤', SanitizeSpecialCharacters::sanitize($text)); - } - - public function testShouldAcceptWhitespaceAfterHeart(): void - { - $text = "\u{2764} "; - $this->assertEquals($text, SanitizeSpecialCharacters::sanitize($text)); - } - - public function testShouldNotClearWhitespaceBetweenHearts(): void - { - $testString = "ā¤ ā¤ ā¤ ā¤ ā¤"; - $this->assertEquals($testString, SanitizeSpecialCharacters::sanitize($testString)); - } - - public function testShouldNotTrimWhitespaceWhenHeartFollowedByLatin(): void - { - $testString = "\u{2764} A"; - $this->assertEquals($testString, SanitizeSpecialCharacters::sanitize($testString)); - } - - public function testShouldNotTrimWhitespaceWhenHeartFollowedByEmoji(): void - { - $testString = "\u{2764} 🟧"; - $this->assertEquals($testString, SanitizeSpecialCharacters::sanitize($testString)); - } - - public function testShouldConvertUnsupportedEmojisToWhitespace(): void - { - $testString = "ā˜ ļøāš ļøāœ…ā–¶ļøāœØāŒ›ļø"; - $equivalentWhitespace = " "; // 6 spaces - $this->assertEquals($equivalentWhitespace, SanitizeSpecialCharacters::sanitize($testString)); - } - - public function testShouldHandleHeartEmojiAndUnsupportedEmojis(): void - { - $testString = "ā¤ļøā˜ ļøāš ļøāœ…ā–¶ļøāœØāŒ›ļø"; - $expectation = "\u{2764} "; // U+2764 + 6 spaces - $this->assertEquals($expectation, SanitizeSpecialCharacters::sanitize($testString)); - } - - public function testShouldSanitizeGermanAndSpecialCharacters(): void - { - $this->assertEquals('AE', SanitizeSpecialCharacters::sanitize('Ƥ')); - $this->assertEquals('AE', SanitizeSpecialCharacters::sanitize('Ƅ')); - $this->assertEquals('OE', SanitizeSpecialCharacters::sanitize('ƶ')); - $this->assertEquals('OE', SanitizeSpecialCharacters::sanitize('Ɩ')); - $this->assertEquals('UE', SanitizeSpecialCharacters::sanitize('ü')); - $this->assertEquals('UE', SanitizeSpecialCharacters::sanitize('Ü')); - $this->assertEquals('SS', SanitizeSpecialCharacters::sanitize('ß')); - - $this->assertEquals('o', SanitizeSpecialCharacters::sanitize('Ćø')); - $this->assertEquals('a', SanitizeSpecialCharacters::sanitize('Ć„')); - - $this->assertEquals('OE', SanitizeSpecialCharacters::sanitize('œ')); - $this->assertEquals('AE', SanitizeSpecialCharacters::sanitize('Ʀ')); - - $this->assertEquals('c', SanitizeSpecialCharacters::sanitize('Ƨ')); - $this->assertEquals('f', SanitizeSpecialCharacters::sanitize('ʒ')); - $this->assertEquals(' ', SanitizeSpecialCharacters::sanitize('µ')); - - $this->assertEquals('...', SanitizeSpecialCharacters::sanitize('…')); - $this->assertEquals('-', SanitizeSpecialCharacters::sanitize('–')); - $this->assertEquals('/', SanitizeSpecialCharacters::sanitize('⁄')); - - $allChars = "Ć¤Ć„Ć¶Ć–Ć¼ĆœĆŸĆøĆ„Å“Ć¦Ć§Ę’Āµā€¦ā€“ā„āˆ‘Ā”Ā¶Ā¢[]|{}ā‰ Āæā‚¬Ā®ā€ ĀØĻ€ā€¢Ā±āˆ‚Ā©Āŗāˆ†@Ā„ā‰ˆāˆšāˆ«~āˆž"; - $result = SanitizeSpecialCharacters::sanitize($allChars); - $this->assertNotEmpty($result); - $this->assertIsString($result); - $this->assertDoesNotMatchRegularExpression('/[Ć¤Ć„Ć¶Ć–Ć¼ĆœĆŸĆøĆ„Å“Ć¦Ć§Ę’Āµ]/u', $result); - } - - public function testShouldHandleGermanTextWithUmlauts(): void - { - $germanText = 'Über die Brücke gehen wir für Ɩsterreich'; - $result = SanitizeSpecialCharacters::sanitize($germanText); - $this->assertEquals('UEber die BrUEcke gehen wir fUEr OEsterreich', $result); - } - - public function testShouldHandleGermanSharpS(): void - { - $germanText = 'Straße'; - $result = SanitizeSpecialCharacters::sanitize($germanText); - $this->assertEquals('StraSSe', $result); - } - - public function testShouldConvertScharfesToSS(): void - { - $texts = ['ß', 'Straße', 'fußball', 'groß', 'weiß']; - $expected = ['SS', 'StraSSe', 'fuSSball', 'groSS', 'weiSS']; - - foreach ($texts as $i => $text) { - $this->assertEquals($expected[$i], SanitizeSpecialCharacters::sanitize($text)); - } - } -} diff --git a/php/tests/VbmlTest.php b/php/tests/VbmlTest.php deleted file mode 100644 index bfb199e..0000000 --- a/php/tests/VbmlTest.php +++ /dev/null @@ -1,329 +0,0 @@ - ['height' => 1, 'width' => 2], - 'components' => [['template' => 'hi']], - ]); - $this->assertEquals([[8, 9]], $result); - } - - public function testShouldLayoutComponentsSideBySide(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 1, 'width' => 4], - 'components' => [ - ['template' => 'hi', 'style' => ['width' => 2, 'height' => 1]], - ['template' => 'hi', 'style' => ['width' => 2, 'height' => 1]], - ], - ]); - $this->assertEquals([[8, 9, 8, 9]], $result); - } - - public function testShouldFormatAeAe(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 1, 'width' => 4], - 'components' => [ - ['template' => 'ƤƄ', 'style' => ['width' => 4, 'height' => 1]], - ], - ]); - $this->assertEquals([[1, 5, 1, 5]], $result); - } - - public function testShouldLayoutComponentsVertically(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 2, 'width' => 2], - 'components' => [ - ['template' => 'hi', 'style' => ['width' => 2, 'height' => 1]], - ['template' => 'hi', 'style' => ['width' => 2, 'height' => 1]], - ], - ]); - $this->assertEquals([[8, 9], [8, 9]], $result); - } - - public function testShouldFlowThirdComponentToNextLine(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 2, 'width' => 4], - 'components' => [ - ['template' => '{1}{2}', 'style' => ['width' => 2, 'height' => 1]], - ['template' => '{3}{4}', 'style' => ['width' => 2, 'height' => 1]], - ['template' => '{5}{6}', 'style' => ['width' => 2, 'height' => 1]], - ], - ]); - $this->assertEquals([[1, 2, 3, 4], [5, 6, 0, 0]], $result); - } - - public function testShouldJustifyContentVertically(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 5, 'width' => 1], - 'components' => [ - ['template' => 'abcd', 'style' => ['height' => 5, 'width' => 1, 'align' => 'justified']], - ], - ]); - $this->assertEquals([[0], [1], [2], [3], [4]], $result); - } - - public function testShouldJustifyWithThreeCharsAndRows(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 5, 'width' => 1], - 'components' => [ - ['template' => 'abc', 'style' => ['height' => 5, 'width' => 1, 'align' => 'justified']], - ], - ]); - $this->assertEquals([[0], [1], [2], [3], [0]], $result); - } - - public function testShouldLayoutAbsoluteByRelative(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 22, 'width' => 6], - 'components' => [ - ['template' => 'abc', 'style' => ['height' => 6, 'width' => 22, 'align' => 'top', 'justify' => 'left']], - ['template' => 'def', 'style' => ['height' => 1, 'width' => 3, 'align' => 'top', 'justify' => 'left', 'absolutePosition' => ['x' => 3, 'y' => 0]]], - ], - ]); - $expected = [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - $this->assertEquals($expected, $result[0]); - } - - public function testShouldLayoutAbsoluteOverRelative(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 22, 'width' => 6], - 'components' => [ - ['template' => 'abc', 'style' => ['height' => 6, 'width' => 22, 'align' => 'top', 'justify' => 'left']], - ['template' => 'def', 'style' => ['height' => 1, 'width' => 3, 'align' => 'top', 'justify' => 'left', 'absolutePosition' => ['x' => 0, 'y' => 0]]], - ], - ]); - $expected = [4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - $this->assertEquals($expected, $result[0]); - } - - public function testShouldLayoutAbsoluteOverRelative2(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 6, 'width' => 22], - 'components' => [ - ['template' => 'abc', 'style' => ['height' => 6, 'width' => 22, 'align' => 'top', 'justify' => 'left']], - ['template' => 'def', 'style' => ['height' => 1, 'width' => 3, 'align' => 'top', 'justify' => 'left', 'absolutePosition' => ['x' => 0, 'y' => 0]]], - ], - ]); - $expected = [4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - $this->assertEquals($expected, $result[0]); - } - - public function testShouldLayoutRawComponents(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 6, 'width' => 22], - 'components' => [ - ['rawCharacters' => [[1, 2, 3]]], - ], - ]); - $expected = [1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - $this->assertEquals($expected, $result[0]); - } - - public function testShouldLayoutAbsoluteWithRawComponentsForClock(): void - { - $result = Vbml::parse([ - 'props' => ['time' => '12:00 PM'], - 'style' => ['height' => 6, 'width' => 22], - 'components' => [ - ['rawCharacters' => [ - [68, 68, 68, 68, 68, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68], - [68, 68, 68, 68, 69, 69, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 65, 65, 65, 65, 68, 68], - [63, 63, 63, 69, 66, 69, 66, 69, 69, 63, 63, 63, 63, 63, 63, 65, 65, 65, 65, 65, 65, 63], - [63, 63, 66, 66, 66, 69, 66, 66, 66, 66, 63, 63, 63, 63, 63, 65, 65, 65, 65, 65, 65, 63], - [64, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, 65, 65, 65, 65, 64, 64], - [66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], - ]], - ['template' => '{{time}}', 'style' => ['height' => 1, 'width' => 8, 'absolutePosition' => ['x' => 11, 'y' => 3]]], - ], - ]); - $this->assertEquals([ - [68, 68, 68, 68, 68, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68], - [68, 68, 68, 68, 69, 69, 69, 69, 68, 68, 68, 68, 68, 68, 68, 68, 65, 65, 65, 65, 68, 68], - [63, 63, 63, 69, 66, 69, 66, 69, 69, 63, 63, 63, 63, 63, 63, 65, 65, 65, 65, 65, 65, 63], - [63, 63, 66, 66, 66, 69, 66, 66, 66, 66, 63, 27, 28, 50, 36, 36, 0, 16, 13, 65, 65, 63], - [64, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, 65, 65, 65, 65, 64, 64], - [66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], - ], $result); - } - - public function testShouldLayoutCalendarForChristmas(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 6, 'width' => 22], - 'components' => [ - [ - 'calendar' => [ - 'defaultDayColor' => 66, - 'month' => '12', - 'year' => '2024', - 'days' => ['25' => 63], - ], - 'style' => ['absolutePosition' => ['x' => 0, 'y' => 0]], - ], - ], - ]); - $this->assertEquals([ - [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 27, 44, 33, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 34, 44, 27, 30, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [27, 31, 44, 28, 27, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 28, 44, 28, 34, 66, 66, 66, 63, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 35, 44, 29, 27, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], $result); - - foreach ($result as $row) { - $this->assertCount(22, $row); - } - } - - public function testShouldLayoutMinimalistCalendar(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 6, 'width' => 22], - 'components' => [ - [ - 'style' => ['absolutePosition' => ['x' => 0, 'y' => 0]], - 'calendar' => [ - 'defaultDayColor' => 66, - 'hideDates' => true, - 'hideMonthYear' => true, - 'hideSMTWTFS' => true, - 'month' => '12', - 'year' => '2024', - 'days' => ['25' => 63], - ], - ], - ], - ]); - $this->assertEquals([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 63, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], $result); - - foreach ($result as $row) { - $this->assertCount(22, $row); - } - } - - public function testShouldLayoutCalendarWithOtherComponents(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 6, 'width' => 22], - 'components' => [ - [ - 'template' => 'December 2024 Calendar', - 'style' => ['height' => 6, 'width' => 10, 'absolutePosition' => ['x' => 13, 'y' => 0]], - ], - [ - 'calendar' => [ - 'month' => '12', - 'year' => '2024', - 'days' => ['1' => 63, '2' => 64, '3' => 65, '4' => 66, '5' => 67, '6' => 68, '7' => 63], - ], - 'style' => ['absolutePosition' => ['x' => 0, 'y' => 0]], - ], - ], - ]); - $this->assertEquals([ - [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 4, 5, 3, 5, 13, 2, 5, 18, 0], - [0, 27, 44, 33, 0, 63, 64, 65, 66, 67, 68, 63, 0, 28, 36, 28, 30, 0, 0, 0, 0, 0], - [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 3, 1, 12, 5, 14, 4, 1, 18, 0], - [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [28, 35, 44, 29, 27, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ], $result); - - foreach ($result as $row) { - $this->assertCount(22, $row); - } - } - - public function testShouldLayoutCalendarOnTheRight(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 6, 'width' => 22], - 'components' => [ - [ - 'template' => 'Merry Christmas', - 'style' => ['height' => 6, 'width' => 10, 'absolutePosition' => ['x' => 0, 'y' => 0]], - ], - [ - 'calendar' => [ - 'defaultDayColor' => 66, - 'month' => '12', - 'year' => '2028', - 'days' => ['25' => 63], - ], - 'style' => ['absolutePosition' => ['x' => 10, 'y' => 0]], - ], - ], - ]); - $this->assertEquals([ - [13, 5, 18, 18, 25, 0, 0, 0, 0, 0, 27, 28, 59, 28, 34, 19, 13, 20, 23, 20, 6, 19], - [3, 8, 18, 9, 19, 20, 13, 1, 19, 0, 0, 27, 44, 28, 0, 0, 0, 0, 0, 0, 66, 66], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 44, 35, 0, 66, 66, 66, 66, 66, 66, 66], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 36, 44, 27, 32, 66, 66, 66, 66, 66, 66, 66], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 33, 44, 28, 29, 66, 66, 66, 66, 66, 66, 66], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 30, 44, 29, 27, 66, 63, 66, 66, 66, 66, 66], - ], $result); - - foreach ($result as $row) { - $this->assertCount(22, $row); - } - } - - public function testShouldRespectDoubleReturns(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 3, 'width' => 2], - 'components' => [ - ['template' => "h\n\ni", 'style' => ['align' => 'top', 'justify' => 'left']], - ], - ]); - $this->assertEquals([[8, 0], [0, 0], [9, 0]], $result); - } - - public function testShouldRespectTripleReturns(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 4, 'width' => 2], - 'components' => [ - ['template' => "h\n\n\ni", 'style' => ['align' => 'top', 'justify' => 'left']], - ], - ]); - $this->assertEquals([[8, 0], [0, 0], [0, 0], [9, 0]], $result); - } - - public function testShouldLetUsUseRandomColors(): void - { - $result = Vbml::parse([ - 'style' => ['height' => 1, 'width' => 1], - 'components' => [ - ['randomColors' => ['colors' => [61]]], - ], - ]); - $this->assertSame(61, $result[0][0]); - } -} From 038f88f5222652ff203009f98ee8aa97448ff3b6 Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Wed, 18 Mar 2026 22:50:46 -0700 Subject: [PATCH 06/12] ci(conformance): add cross-platform validation workflow --- .github/workflows/conformance.yml | 85 +++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 .github/workflows/conformance.yml diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml new file mode 100644 index 0000000..902a708 --- /dev/null +++ b/.github/workflows/conformance.yml @@ -0,0 +1,85 @@ +name: Conformance + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + fixtures: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22.x" + cache: yarn + + - name: Enable Corepack + run: corepack enable + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Validate Shared Fixtures + run: yarn test:fixtures + + node: + needs: fixtures + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22.x" + cache: yarn + + - name: Enable Corepack + run: corepack enable + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Run Node Tests + run: yarn test --runInBand + + python: + needs: fixtures + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Test Dependencies + run: python -m pip install pytest + + - name: Run Python Conformance Tests + env: + PYTHONPATH: ${{ github.workspace }}/python + run: pytest python/tests/conformance -q + + php: + needs: fixtures + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + tools: composer + + - name: Install Dependencies + working-directory: php + run: composer install --no-interaction --no-progress + + - name: Run PHP Tests + working-directory: php + run: ./vendor/bin/phpunit From 4613aad94a4c96005a94f6e7c8fc5cac6e6fcdc8 Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Thu, 19 Mar 2026 00:07:34 -0700 Subject: [PATCH 07/12] test(conformance): cover conversion edge cases --- .../renders_filled_cells_on_white_board.json | 3 +++ ...s_filled_cells_with_default_board_color.json | 3 +++ .../ignores_color_codes.json | 3 +++ ...ns_empty_string_for_breakable_rows_only.json | 3 +++ .../treats_filled_cells_as_spaces.json | 3 +++ .../renders_filled_cells_on_white_board.json | 10 ++++++++++ ...s_filled_cells_with_default_board_color.json | 9 +++++++++ .../ignores_color_codes.json | 10 ++++++++++ ...ns_empty_string_for_breakable_rows_only.json | 17 +++++++++++++++++ .../treats_filled_cells_as_spaces.json | 9 +++++++++ 10 files changed, 70 insertions(+) create mode 100644 test/expected/characterCodesToAscii/renders_filled_cells_on_white_board.json create mode 100644 test/expected/characterCodesToAscii/renders_filled_cells_with_default_board_color.json create mode 100644 test/expected/characterCodesToString/ignores_color_codes.json create mode 100644 test/expected/characterCodesToString/returns_empty_string_for_breakable_rows_only.json create mode 100644 test/expected/characterCodesToString/treats_filled_cells_as_spaces.json create mode 100644 test/input/characterCodesToAscii/renders_filled_cells_on_white_board.json create mode 100644 test/input/characterCodesToAscii/renders_filled_cells_with_default_board_color.json create mode 100644 test/input/characterCodesToString/ignores_color_codes.json create mode 100644 test/input/characterCodesToString/returns_empty_string_for_breakable_rows_only.json create mode 100644 test/input/characterCodesToString/treats_filled_cells_as_spaces.json diff --git a/test/expected/characterCodesToAscii/renders_filled_cells_on_white_board.json b/test/expected/characterCodesToAscii/renders_filled_cells_on_white_board.json new file mode 100644 index 0000000..2fdaa4f --- /dev/null +++ b/test/expected/characterCodesToAscii/renders_filled_cells_on_white_board.json @@ -0,0 +1,3 @@ +{ + "result": "⬛ ⬛" +} diff --git a/test/expected/characterCodesToAscii/renders_filled_cells_with_default_board_color.json b/test/expected/characterCodesToAscii/renders_filled_cells_with_default_board_color.json new file mode 100644 index 0000000..c575934 --- /dev/null +++ b/test/expected/characterCodesToAscii/renders_filled_cells_with_default_board_color.json @@ -0,0 +1,3 @@ +{ + "result": "⬜ ⬜" +} diff --git a/test/expected/characterCodesToString/ignores_color_codes.json b/test/expected/characterCodesToString/ignores_color_codes.json new file mode 100644 index 0000000..d0b482d --- /dev/null +++ b/test/expected/characterCodesToString/ignores_color_codes.json @@ -0,0 +1,3 @@ +{ + "result": "AB" +} diff --git a/test/expected/characterCodesToString/returns_empty_string_for_breakable_rows_only.json b/test/expected/characterCodesToString/returns_empty_string_for_breakable_rows_only.json new file mode 100644 index 0000000..d726a1a --- /dev/null +++ b/test/expected/characterCodesToString/returns_empty_string_for_breakable_rows_only.json @@ -0,0 +1,3 @@ +{ + "result": "" +} diff --git a/test/expected/characterCodesToString/treats_filled_cells_as_spaces.json b/test/expected/characterCodesToString/treats_filled_cells_as_spaces.json new file mode 100644 index 0000000..32a7af8 --- /dev/null +++ b/test/expected/characterCodesToString/treats_filled_cells_as_spaces.json @@ -0,0 +1,3 @@ +{ + "result": "A B" +} diff --git a/test/input/characterCodesToAscii/renders_filled_cells_on_white_board.json b/test/input/characterCodesToAscii/renders_filled_cells_on_white_board.json new file mode 100644 index 0000000..cf40019 --- /dev/null +++ b/test/input/characterCodesToAscii/renders_filled_cells_on_white_board.json @@ -0,0 +1,10 @@ +{ + "characterCodes": [ + [ + 71, + 0, + 71 + ] + ], + "isWhite": true +} diff --git a/test/input/characterCodesToAscii/renders_filled_cells_with_default_board_color.json b/test/input/characterCodesToAscii/renders_filled_cells_with_default_board_color.json new file mode 100644 index 0000000..e6897f2 --- /dev/null +++ b/test/input/characterCodesToAscii/renders_filled_cells_with_default_board_color.json @@ -0,0 +1,9 @@ +{ + "characterCodes": [ + [ + 71, + 0, + 71 + ] + ] +} diff --git a/test/input/characterCodesToString/ignores_color_codes.json b/test/input/characterCodesToString/ignores_color_codes.json new file mode 100644 index 0000000..b7b79fc --- /dev/null +++ b/test/input/characterCodesToString/ignores_color_codes.json @@ -0,0 +1,10 @@ +{ + "characters": [ + [ + 1, + 63, + 64, + 2 + ] + ] +} diff --git a/test/input/characterCodesToString/returns_empty_string_for_breakable_rows_only.json b/test/input/characterCodesToString/returns_empty_string_for_breakable_rows_only.json new file mode 100644 index 0000000..1653365 --- /dev/null +++ b/test/input/characterCodesToString/returns_empty_string_for_breakable_rows_only.json @@ -0,0 +1,17 @@ +{ + "characters": [ + [ + 0, + 63, + 71 + ], + [ + 70, + 69, + 0 + ] + ], + "options": { + "allowLineBreaks": true + } +} diff --git a/test/input/characterCodesToString/treats_filled_cells_as_spaces.json b/test/input/characterCodesToString/treats_filled_cells_as_spaces.json new file mode 100644 index 0000000..53b0a63 --- /dev/null +++ b/test/input/characterCodesToString/treats_filled_cells_as_spaces.json @@ -0,0 +1,9 @@ +{ + "characters": [ + [ + 1, + 71, + 2 + ] + ] +} From a57c0299d1dfcd29cf428b7ab1ea869c5e06c24d Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Thu, 19 Mar 2026 00:11:51 -0700 Subject: [PATCH 08/12] test(conformance): cover calendar display variants --- .../applies_default_day_color_to_visible_days.json | 10 ++++++++++ test/expected/calendar/hides_dates.json | 10 ++++++++++ test/expected/calendar/hides_month_year.json | 10 ++++++++++ test/expected/calendar/hides_weekday_header.json | 10 ++++++++++ ...ngle_date_header_when_month_starts_on_saturday.json | 10 ++++++++++ .../applies_default_day_color_to_visible_days.json | 6 ++++++ test/input/calendar/hides_dates.json | 6 ++++++ test/input/calendar/hides_month_year.json | 6 ++++++ test/input/calendar/hides_weekday_header.json | 6 ++++++ ...ngle_date_header_when_month_starts_on_saturday.json | 5 +++++ 10 files changed, 79 insertions(+) create mode 100644 test/expected/calendar/applies_default_day_color_to_visible_days.json create mode 100644 test/expected/calendar/hides_dates.json create mode 100644 test/expected/calendar/hides_month_year.json create mode 100644 test/expected/calendar/hides_weekday_header.json create mode 100644 test/expected/calendar/renders_single_date_header_when_month_starts_on_saturday.json create mode 100644 test/input/calendar/applies_default_day_color_to_visible_days.json create mode 100644 test/input/calendar/hides_dates.json create mode 100644 test/input/calendar/hides_month_year.json create mode 100644 test/input/calendar/hides_weekday_header.json create mode 100644 test/input/calendar/renders_single_date_header_when_month_starts_on_saturday.json diff --git a/test/expected/calendar/applies_default_day_color_to_visible_days.json b/test/expected/calendar/applies_default_day_color_to_visible_days.json new file mode 100644 index 0000000..365c65b --- /dev/null +++ b/test/expected/calendar/applies_default_day_color_to_visible_days.json @@ -0,0 +1,10 @@ +{ + "result": [ + [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 33, 0, 67, 67, 67, 67, 67, 67, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 34, 44, 27, 30, 67, 67, 67, 67, 67, 67, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 31, 44, 28, 27, 67, 67, 67, 67, 67, 67, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 28, 44, 28, 34, 67, 67, 67, 67, 67, 67, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 35, 44, 29, 27, 67, 67, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/calendar/hides_dates.json b/test/expected/calendar/hides_dates.json new file mode 100644 index 0000000..5e992f4 --- /dev/null +++ b/test/expected/calendar/hides_dates.json @@ -0,0 +1,10 @@ +{ + "result": [ + [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/calendar/hides_month_year.json b/test/expected/calendar/hides_month_year.json new file mode 100644 index 0000000..8e1ee49 --- /dev/null +++ b/test/expected/calendar/hides_month_year.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 35, 44, 29, 27, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/calendar/hides_weekday_header.json b/test/expected/calendar/hides_weekday_header.json new file mode 100644 index 0000000..cda1800 --- /dev/null +++ b/test/expected/calendar/hides_weekday_header.json @@ -0,0 +1,10 @@ +{ + "result": [ + [27, 28, 59, 28, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 28, 44, 28, 34, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 35, 44, 29, 27, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/calendar/renders_single_date_header_when_month_starts_on_saturday.json b/test/expected/calendar/renders_single_date_header_when_month_starts_on_saturday.json new file mode 100644 index 0000000..5b51cff --- /dev/null +++ b/test/expected/calendar/renders_single_date_header_when_month_starts_on_saturday.json @@ -0,0 +1,10 @@ +{ + "result": [ + [28, 59, 28, 31, 0, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 28, 44, 34, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 35, 44, 27, 31, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 32, 44, 28, 28, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 29, 44, 28, 34, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/input/calendar/applies_default_day_color_to_visible_days.json b/test/input/calendar/applies_default_day_color_to_visible_days.json new file mode 100644 index 0000000..43a4ca3 --- /dev/null +++ b/test/input/calendar/applies_default_day_color_to_visible_days.json @@ -0,0 +1,6 @@ +{ + "month": "12", + "year": "2024", + "days": {}, + "defaultDayColor": 67 +} diff --git a/test/input/calendar/hides_dates.json b/test/input/calendar/hides_dates.json new file mode 100644 index 0000000..215f421 --- /dev/null +++ b/test/input/calendar/hides_dates.json @@ -0,0 +1,6 @@ +{ + "month": "12", + "year": "2024", + "days": {}, + "hideDates": true +} diff --git a/test/input/calendar/hides_month_year.json b/test/input/calendar/hides_month_year.json new file mode 100644 index 0000000..d19b637 --- /dev/null +++ b/test/input/calendar/hides_month_year.json @@ -0,0 +1,6 @@ +{ + "month": "12", + "year": "2024", + "days": {}, + "hideMonthYear": true +} diff --git a/test/input/calendar/hides_weekday_header.json b/test/input/calendar/hides_weekday_header.json new file mode 100644 index 0000000..64c6009 --- /dev/null +++ b/test/input/calendar/hides_weekday_header.json @@ -0,0 +1,6 @@ +{ + "month": "12", + "year": "2024", + "days": {}, + "hideSMTWTFS": true +} diff --git a/test/input/calendar/renders_single_date_header_when_month_starts_on_saturday.json b/test/input/calendar/renders_single_date_header_when_month_starts_on_saturday.json new file mode 100644 index 0000000..2c14d5b --- /dev/null +++ b/test/input/calendar/renders_single_date_header_when_month_starts_on_saturday.json @@ -0,0 +1,5 @@ +{ + "month": "2", + "year": "2025", + "days": {} +} From 1cc3c5838fea9a75fc24304fc0198eda36749f8c Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Thu, 19 Mar 2026 00:15:42 -0700 Subject: [PATCH 09/12] test(conformance): cover vbml defaults and clipping --- .../defaults_calendar_position_to_origin.json | 10 +++++++ ...defaults_missing_absolute_x_to_origin.json | 5 ++++ ...defaults_missing_absolute_y_to_origin.json | 5 ++++ ...y_flagship_board_for_empty_components.json | 10 +++++++ ...ncates_absolute_component_below_board.json | 6 ++++ ...ncates_calendar_component_below_board.json | 6 ++++ ...s_default_flagship_size_without_style.json | 10 +++++++ .../defaults_calendar_position_to_origin.json | 17 +++++++++++ ...defaults_missing_absolute_x_to_origin.json | 29 +++++++++++++++++++ ...defaults_missing_absolute_y_to_origin.json | 29 +++++++++++++++++++ ...y_flagship_board_for_empty_components.json | 3 ++ ...ncates_absolute_component_below_board.json | 26 +++++++++++++++++ ...ncates_calendar_component_below_board.json | 17 +++++++++++ ...s_default_flagship_size_without_style.json | 12 ++++++++ .../defaults_calendar_position_to_origin.json | 6 ++++ ...ncates_calendar_component_below_board.json | 6 ++++ 16 files changed, 197 insertions(+) create mode 100644 test/expected/vbml/defaults_calendar_position_to_origin.json create mode 100644 test/expected/vbml/defaults_missing_absolute_x_to_origin.json create mode 100644 test/expected/vbml/defaults_missing_absolute_y_to_origin.json create mode 100644 test/expected/vbml/returns_empty_flagship_board_for_empty_components.json create mode 100644 test/expected/vbml/truncates_absolute_component_below_board.json create mode 100644 test/expected/vbml/truncates_calendar_component_below_board.json create mode 100644 test/expected/vbml/uses_default_flagship_size_without_style.json create mode 100644 test/input/vbml/defaults_calendar_position_to_origin.json create mode 100644 test/input/vbml/defaults_missing_absolute_x_to_origin.json create mode 100644 test/input/vbml/defaults_missing_absolute_y_to_origin.json create mode 100644 test/input/vbml/returns_empty_flagship_board_for_empty_components.json create mode 100644 test/input/vbml/truncates_absolute_component_below_board.json create mode 100644 test/input/vbml/truncates_calendar_component_below_board.json create mode 100644 test/input/vbml/uses_default_flagship_size_without_style.json create mode 100644 test/platform-exceptions/python/vbml/defaults_calendar_position_to_origin.json create mode 100644 test/platform-exceptions/python/vbml/truncates_calendar_component_below_board.json diff --git a/test/expected/vbml/defaults_calendar_position_to_origin.json b/test/expected/vbml/defaults_calendar_position_to_origin.json new file mode 100644 index 0000000..4f986ea --- /dev/null +++ b/test/expected/vbml/defaults_calendar_position_to_origin.json @@ -0,0 +1,10 @@ +{ + "result": [ + [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 34, 44, 27, 30, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [27, 31, 44, 28, 27, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 28, 44, 28, 34, 65, 65, 65, 63, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [28, 35, 44, 29, 27, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/defaults_missing_absolute_x_to_origin.json b/test/expected/vbml/defaults_missing_absolute_x_to_origin.json new file mode 100644 index 0000000..432f10f --- /dev/null +++ b/test/expected/vbml/defaults_missing_absolute_x_to_origin.json @@ -0,0 +1,5 @@ +{ + "result": [ + [1, 2, 0, 0] + ] +} diff --git a/test/expected/vbml/defaults_missing_absolute_y_to_origin.json b/test/expected/vbml/defaults_missing_absolute_y_to_origin.json new file mode 100644 index 0000000..432f10f --- /dev/null +++ b/test/expected/vbml/defaults_missing_absolute_y_to_origin.json @@ -0,0 +1,5 @@ +{ + "result": [ + [1, 2, 0, 0] + ] +} diff --git a/test/expected/vbml/returns_empty_flagship_board_for_empty_components.json b/test/expected/vbml/returns_empty_flagship_board_for_empty_components.json new file mode 100644 index 0000000..519d99a --- /dev/null +++ b/test/expected/vbml/returns_empty_flagship_board_for_empty_components.json @@ -0,0 +1,10 @@ +{ + "result": [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/truncates_absolute_component_below_board.json b/test/expected/vbml/truncates_absolute_component_below_board.json new file mode 100644 index 0000000..15eaebe --- /dev/null +++ b/test/expected/vbml/truncates_absolute_component_below_board.json @@ -0,0 +1,6 @@ +{ + "result": [ + [0, 0, 0], + [0, 1, 2] + ] +} diff --git a/test/expected/vbml/truncates_calendar_component_below_board.json b/test/expected/vbml/truncates_calendar_component_below_board.json new file mode 100644 index 0000000..a3a22c5 --- /dev/null +++ b/test/expected/vbml/truncates_calendar_component_below_board.json @@ -0,0 +1,6 @@ +{ + "result": [ + [27, 28, 59, 28, 30, 19, 13, 20, 23, 20, 6, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 27, 44, 33, 0, 65, 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/expected/vbml/uses_default_flagship_size_without_style.json b/test/expected/vbml/uses_default_flagship_size_without_style.json new file mode 100644 index 0000000..86ab7e1 --- /dev/null +++ b/test/expected/vbml/uses_default_flagship_size_without_style.json @@ -0,0 +1,10 @@ +{ + "result": [ + [8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/input/vbml/defaults_calendar_position_to_origin.json b/test/input/vbml/defaults_calendar_position_to_origin.json new file mode 100644 index 0000000..4b0b94e --- /dev/null +++ b/test/input/vbml/defaults_calendar_position_to_origin.json @@ -0,0 +1,17 @@ +{ + "style": { + "height": 6, + "width": 22 + }, + "components": [ + { + "calendar": { + "month": "12", + "year": "2024", + "days": { + "25": 63 + } + } + } + ] +} diff --git a/test/input/vbml/defaults_missing_absolute_x_to_origin.json b/test/input/vbml/defaults_missing_absolute_x_to_origin.json new file mode 100644 index 0000000..ecd8123 --- /dev/null +++ b/test/input/vbml/defaults_missing_absolute_x_to_origin.json @@ -0,0 +1,29 @@ +{ + "style": { + "height": 1, + "width": 4 + }, + "components": [ + { + "rawCharacters": [ + [ + 3, + 4 + ] + ] + }, + { + "rawCharacters": [ + [ + 1, + 2 + ] + ], + "style": { + "absolutePosition": { + "y": 0 + } + } + } + ] +} diff --git a/test/input/vbml/defaults_missing_absolute_y_to_origin.json b/test/input/vbml/defaults_missing_absolute_y_to_origin.json new file mode 100644 index 0000000..863ec28 --- /dev/null +++ b/test/input/vbml/defaults_missing_absolute_y_to_origin.json @@ -0,0 +1,29 @@ +{ + "style": { + "height": 1, + "width": 4 + }, + "components": [ + { + "rawCharacters": [ + [ + 3, + 4 + ] + ] + }, + { + "rawCharacters": [ + [ + 1, + 2 + ] + ], + "style": { + "absolutePosition": { + "x": 0 + } + } + } + ] +} diff --git a/test/input/vbml/returns_empty_flagship_board_for_empty_components.json b/test/input/vbml/returns_empty_flagship_board_for_empty_components.json new file mode 100644 index 0000000..026b5e8 --- /dev/null +++ b/test/input/vbml/returns_empty_flagship_board_for_empty_components.json @@ -0,0 +1,3 @@ +{ + "components": [] +} diff --git a/test/input/vbml/truncates_absolute_component_below_board.json b/test/input/vbml/truncates_absolute_component_below_board.json new file mode 100644 index 0000000..c758727 --- /dev/null +++ b/test/input/vbml/truncates_absolute_component_below_board.json @@ -0,0 +1,26 @@ +{ + "style": { + "height": 2, + "width": 3 + }, + "components": [ + { + "rawCharacters": [ + [ + 1, + 2 + ], + [ + 3, + 4 + ] + ], + "style": { + "absolutePosition": { + "x": 1, + "y": 1 + } + } + } + ] +} diff --git a/test/input/vbml/truncates_calendar_component_below_board.json b/test/input/vbml/truncates_calendar_component_below_board.json new file mode 100644 index 0000000..380ca4a --- /dev/null +++ b/test/input/vbml/truncates_calendar_component_below_board.json @@ -0,0 +1,17 @@ +{ + "style": { + "height": 2, + "width": 22 + }, + "components": [ + { + "calendar": { + "month": "12", + "year": "2024", + "days": { + "25": 63 + } + } + } + ] +} diff --git a/test/input/vbml/uses_default_flagship_size_without_style.json b/test/input/vbml/uses_default_flagship_size_without_style.json new file mode 100644 index 0000000..23b5f64 --- /dev/null +++ b/test/input/vbml/uses_default_flagship_size_without_style.json @@ -0,0 +1,12 @@ +{ + "components": [ + { + "rawCharacters": [ + [ + 8, + 9 + ] + ] + } + ] +} diff --git a/test/platform-exceptions/python/vbml/defaults_calendar_position_to_origin.json b/test/platform-exceptions/python/vbml/defaults_calendar_position_to_origin.json new file mode 100644 index 0000000..545e8a0 --- /dev/null +++ b/test/platform-exceptions/python/vbml/defaults_calendar_position_to_origin.json @@ -0,0 +1,6 @@ +{ + "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", + "error": { + "message": "'<=' not supported between instances of 'int' and 'str'" + } +} diff --git a/test/platform-exceptions/python/vbml/truncates_calendar_component_below_board.json b/test/platform-exceptions/python/vbml/truncates_calendar_component_below_board.json new file mode 100644 index 0000000..545e8a0 --- /dev/null +++ b/test/platform-exceptions/python/vbml/truncates_calendar_component_below_board.json @@ -0,0 +1,6 @@ +{ + "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", + "error": { + "message": "'<=' not supported between instances of 'int' and 'str'" + } +} From 919ac1a597194a338ab3433c3dbbf67ef6ac496f Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Fri, 20 Mar 2026 13:29:19 -0700 Subject: [PATCH 10/12] test(conformance): fix tests to convert calendar ints for python --- python/tests/conformance/support.py | 46 ++++++++++++++++++- python/tests/conformance/test_calendar.py | 4 +- .../defaults_calendar_position_to_origin.json | 6 --- ...outs_calendar_component_for_christmas.json | 6 --- ...youts_calendar_component_on_the_right.json | 6 --- ...endar_component_with_other_components.json | 6 --- ...layouts_minimalist_calendar_component.json | 6 --- ...ncates_calendar_component_below_board.json | 6 --- 8 files changed, 47 insertions(+), 39 deletions(-) delete mode 100644 test/platform-exceptions/python/vbml/defaults_calendar_position_to_origin.json delete mode 100644 test/platform-exceptions/python/vbml/layouts_calendar_component_for_christmas.json delete mode 100644 test/platform-exceptions/python/vbml/layouts_calendar_component_on_the_right.json delete mode 100644 test/platform-exceptions/python/vbml/layouts_calendar_component_with_other_components.json delete mode 100644 test/platform-exceptions/python/vbml/layouts_minimalist_calendar_component.json delete mode 100644 test/platform-exceptions/python/vbml/truncates_calendar_component_below_board.json diff --git a/python/tests/conformance/support.py b/python/tests/conformance/support.py index c4f8215..3ec8bd7 100644 --- a/python/tests/conformance/support.py +++ b/python/tests/conformance/support.py @@ -39,6 +39,50 @@ def _read_json(path: Path) -> Any: return json.loads(path.read_text()) +def _coerce_shared_json_int(value: Any) -> Any: + """Convert shared JSON numeric strings into Python ints.""" + if isinstance(value, str) and value.isdigit(): + return int(value) + return value + + +def _normalize_input_data(suite: str, input_data: Any) -> Any: + """Normalize shared fixture input at the Python conformance boundary.""" + if suite == "calendar" and isinstance(input_data, dict): + return { + **input_data, + "month": _coerce_shared_json_int(input_data["month"]), + "year": _coerce_shared_json_int(input_data["year"]), + } + + if suite == "vbml" and isinstance(input_data, dict): + components = [] + for component in input_data.get("components", []): + if not isinstance(component, dict) or "calendar" not in component: + components.append(component) + continue + + calendar = component.get("calendar") + if not isinstance(calendar, dict): + components.append(component) + continue + + components.append( + { + **component, + "calendar": { + **calendar, + "month": _coerce_shared_json_int(calendar["month"]), + "year": _coerce_shared_json_int(calendar["year"]), + }, + } + ) + + return {**input_data, "components": components} + + return input_data + + def _walk_json_files(root: Path) -> list[Path]: return sorted(path for path in root.rglob("*.json") if path.is_file()) @@ -110,7 +154,7 @@ def load_cases(suite: str, platform: str = "python") -> list[ConformanceCase]: raise FileNotFoundError(f"Missing expected file for {suite}/{relative_path}") case_id = str(relative_path.with_suffix("")).replace("\\", "/") - input_data = _read_json(input_path) + input_data = _normalize_input_data(suite, _read_json(input_path)) expected_payload = _read_json(expected_path) cases.append( diff --git a/python/tests/conformance/test_calendar.py b/python/tests/conformance/test_calendar.py index f09baec..b4d8a0f 100644 --- a/python/tests/conformance/test_calendar.py +++ b/python/tests/conformance/test_calendar.py @@ -11,8 +11,8 @@ def test_calendar(case) -> None: def _run(input_data): return make_calendar( - int(input_data["month"]), - int(input_data["year"]), + input_data["month"], + input_data["year"], default_day_color=input_data.get("defaultDayColor"), highlighted_days=input_data.get("days"), hide_day_of_week=input_data.get("hideSMTWTFS", False), diff --git a/test/platform-exceptions/python/vbml/defaults_calendar_position_to_origin.json b/test/platform-exceptions/python/vbml/defaults_calendar_position_to_origin.json deleted file mode 100644 index 545e8a0..0000000 --- a/test/platform-exceptions/python/vbml/defaults_calendar_position_to_origin.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", - "error": { - "message": "'<=' not supported between instances of 'int' and 'str'" - } -} diff --git a/test/platform-exceptions/python/vbml/layouts_calendar_component_for_christmas.json b/test/platform-exceptions/python/vbml/layouts_calendar_component_for_christmas.json deleted file mode 100644 index 545e8a0..0000000 --- a/test/platform-exceptions/python/vbml/layouts_calendar_component_for_christmas.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", - "error": { - "message": "'<=' not supported between instances of 'int' and 'str'" - } -} diff --git a/test/platform-exceptions/python/vbml/layouts_calendar_component_on_the_right.json b/test/platform-exceptions/python/vbml/layouts_calendar_component_on_the_right.json deleted file mode 100644 index 545e8a0..0000000 --- a/test/platform-exceptions/python/vbml/layouts_calendar_component_on_the_right.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", - "error": { - "message": "'<=' not supported between instances of 'int' and 'str'" - } -} diff --git a/test/platform-exceptions/python/vbml/layouts_calendar_component_with_other_components.json b/test/platform-exceptions/python/vbml/layouts_calendar_component_with_other_components.json deleted file mode 100644 index 545e8a0..0000000 --- a/test/platform-exceptions/python/vbml/layouts_calendar_component_with_other_components.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", - "error": { - "message": "'<=' not supported between instances of 'int' and 'str'" - } -} diff --git a/test/platform-exceptions/python/vbml/layouts_minimalist_calendar_component.json b/test/platform-exceptions/python/vbml/layouts_minimalist_calendar_component.json deleted file mode 100644 index 545e8a0..0000000 --- a/test/platform-exceptions/python/vbml/layouts_minimalist_calendar_component.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", - "error": { - "message": "'<=' not supported between instances of 'int' and 'str'" - } -} diff --git a/test/platform-exceptions/python/vbml/truncates_calendar_component_below_board.json b/test/platform-exceptions/python/vbml/truncates_calendar_component_below_board.json deleted file mode 100644 index 545e8a0..0000000 --- a/test/platform-exceptions/python/vbml/truncates_calendar_component_below_board.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "reason": "Python VBML calendar components pass string month and year values through to make_calendar, which expects integers.", - "error": { - "message": "'<=' not supported between instances of 'int' and 'str'" - } -} From f3960d3846c91ca20e998220bf57d442c95de593 Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Fri, 20 Mar 2026 13:38:17 -0700 Subject: [PATCH 11/12] test(conformance): make shared expected fixtures the source of truth --- scripts/validate-conformance-fixtures.mjs | 5 ---- test/README.md | 16 +++++------ ...ute_components_by_relative_components.json | 12 ++++----- ...uts_absolute_over_relative_components.json | 12 ++++----- ...ute_components_by_relative_components.json | 27 +++++++++++++++++++ ...uts_absolute_over_relative_components.json | 27 +++++++++++++++++++ ...ute_components_by_relative_components.json | 27 ------------------- ...uts_absolute_over_relative_components.json | 27 ------------------- ...ute_components_by_relative_components.json | 27 ------------------- ...uts_absolute_over_relative_components.json | 27 ------------------- 10 files changed, 74 insertions(+), 133 deletions(-) create mode 100644 test/platform-exceptions/node/vbml/layouts_absolute_components_by_relative_components.json create mode 100644 test/platform-exceptions/node/vbml/layouts_absolute_over_relative_components.json delete mode 100644 test/platform-exceptions/php/vbml/layouts_absolute_components_by_relative_components.json delete mode 100644 test/platform-exceptions/php/vbml/layouts_absolute_over_relative_components.json delete mode 100644 test/platform-exceptions/python/vbml/layouts_absolute_components_by_relative_components.json delete mode 100644 test/platform-exceptions/python/vbml/layouts_absolute_over_relative_components.json diff --git a/scripts/validate-conformance-fixtures.mjs b/scripts/validate-conformance-fixtures.mjs index 4b0644c..1b351ba 100644 --- a/scripts/validate-conformance-fixtures.mjs +++ b/scripts/validate-conformance-fixtures.mjs @@ -13,7 +13,6 @@ * - malformed expected error payloads * - malformed platform exceptions * - platform exceptions that do not map to a shared case - * - accidental Node exceptions */ import fs from "fs"; import path from "path"; @@ -110,10 +109,6 @@ const validatePlatformException = (filePath, payload, knownCases) => { const [platform, ...caseSegments] = segments; const caseId = caseSegments.join("/").replace(/\.json$/, ""); - if (platform === "node") { - errors.push(`${fileLabel} is invalid because Node is the shared source of truth.`); - } - if (!knownCases.has(caseId)) { errors.push(`${fileLabel} does not match a shared case in test/input and test/expected.`); } diff --git a/test/README.md b/test/README.md index 10cd0ad..49d6f73 100644 --- a/test/README.md +++ b/test/README.md @@ -3,8 +3,9 @@ The `test/` directory is data-only. It defines the shared behavioral contract for the Node, Python, and PHP implementations. -Node is the source of truth. Shared expectations in `test/expected` should -match Node behavior unless the Node suite itself is being corrected. +Shared expectations in `test/expected` are the source of truth for every +platform. Platform-specific overrides in `test/platform-exceptions` document +temporary or intentional drift from that shared contract. ## Layout @@ -29,10 +30,9 @@ The executable runners live outside of `test/`: ## Platform Exception Rules -Use `test/platform-exceptions` only when a non-Node platform intentionally or -currently behaves differently from the shared expectation. +Use `test/platform-exceptions` only when a platform intentionally or currently +behaves differently from the shared expectation. -- Never add platform exceptions for Node. - Every platform exception must include a non-empty `reason`. - Every platform exception must define exactly one of `result`, `error`, or `skip`. @@ -49,9 +49,9 @@ output, such as deep-copy identity. ## Maintainer Workflow 1. Add or update `test/input//.json`. -2. Add the matching `test/expected//.json` using Node behavior as - the default. -3. If Python or PHP differs, add a matching file under +2. Add the matching `test/expected//.json` for the shared + contract. +3. If any platform differs, add a matching file under `test/platform-exceptions///.json` with a `reason`. 4. Remove the platform exception when that platform matches the shared contract. 5. Run the fixture validator and the affected platform suites. diff --git a/test/expected/vbml/layouts_absolute_components_by_relative_components.json b/test/expected/vbml/layouts_absolute_components_by_relative_components.json index 8d2649d..bc92c99 100644 --- a/test/expected/vbml/layouts_absolute_components_by_relative_components.json +++ b/test/expected/vbml/layouts_absolute_components_by_relative_components.json @@ -1,11 +1,11 @@ { "result": [ - [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 2, 3, 4, 5, 6], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], diff --git a/test/expected/vbml/layouts_absolute_over_relative_components.json b/test/expected/vbml/layouts_absolute_over_relative_components.json index 9265cd2..1df55d6 100644 --- a/test/expected/vbml/layouts_absolute_over_relative_components.json +++ b/test/expected/vbml/layouts_absolute_over_relative_components.json @@ -1,11 +1,11 @@ { "result": [ - [4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [4, 5, 6, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], diff --git a/test/platform-exceptions/node/vbml/layouts_absolute_components_by_relative_components.json b/test/platform-exceptions/node/vbml/layouts_absolute_components_by_relative_components.json new file mode 100644 index 0000000..6f1d7e8 --- /dev/null +++ b/test/platform-exceptions/node/vbml/layouts_absolute_components_by_relative_components.json @@ -0,0 +1,27 @@ +{ + "reason": "This absolute layout case uses truncated 6-column rows in the shared expected fixture, but the Node implementation returns a mixed-width board with 22-column leading rows and 6-column trailing rows.", + "result": [ + [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/platform-exceptions/node/vbml/layouts_absolute_over_relative_components.json b/test/platform-exceptions/node/vbml/layouts_absolute_over_relative_components.json new file mode 100644 index 0000000..26a8009 --- /dev/null +++ b/test/platform-exceptions/node/vbml/layouts_absolute_over_relative_components.json @@ -0,0 +1,27 @@ +{ + "reason": "This absolute layout case uses truncated 6-column rows in the shared expected fixture, but the Node implementation returns a mixed-width board with 22-column leading rows and 6-column trailing rows.", + "result": [ + [4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0] + ] +} diff --git a/test/platform-exceptions/php/vbml/layouts_absolute_components_by_relative_components.json b/test/platform-exceptions/php/vbml/layouts_absolute_components_by_relative_components.json deleted file mode 100644 index 422ea96..0000000 --- a/test/platform-exceptions/php/vbml/layouts_absolute_components_by_relative_components.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "reason": "PHP truncates these 22x6 absolute layout cases to 6-column rows instead of preserving Node's current mixed-width board shape.", - "result": [ - [1, 2, 3, 4, 5, 6], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0] - ] -} diff --git a/test/platform-exceptions/php/vbml/layouts_absolute_over_relative_components.json b/test/platform-exceptions/php/vbml/layouts_absolute_over_relative_components.json deleted file mode 100644 index 500d598..0000000 --- a/test/platform-exceptions/php/vbml/layouts_absolute_over_relative_components.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "reason": "PHP truncates these 22x6 absolute layout cases to 6-column rows instead of preserving Node's current mixed-width board shape.", - "result": [ - [4, 5, 6, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0] - ] -} diff --git a/test/platform-exceptions/python/vbml/layouts_absolute_components_by_relative_components.json b/test/platform-exceptions/python/vbml/layouts_absolute_components_by_relative_components.json deleted file mode 100644 index e55bf5f..0000000 --- a/test/platform-exceptions/python/vbml/layouts_absolute_components_by_relative_components.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "reason": "Python truncates these 22x6 absolute layout cases to 6-column rows instead of preserving Node's current mixed-width board shape.", - "result": [ - [1, 2, 3, 4, 5, 6], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0] - ] -} diff --git a/test/platform-exceptions/python/vbml/layouts_absolute_over_relative_components.json b/test/platform-exceptions/python/vbml/layouts_absolute_over_relative_components.json deleted file mode 100644 index 8559189..0000000 --- a/test/platform-exceptions/python/vbml/layouts_absolute_over_relative_components.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "reason": "Python truncates these 22x6 absolute layout cases to 6-column rows instead of preserving Node's current mixed-width board shape.", - "result": [ - [4, 5, 6, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0] - ] -} From 78c54c1e41dedb6ffe6e6f9a615a834248d6634d Mon Sep 17 00:00:00 2001 From: Brenan Kelley Date: Fri, 20 Mar 2026 13:49:32 -0700 Subject: [PATCH 12/12] test(conformance): rename platform exceptions to language exceptions --- .github/workflows/conformance.yml | 4 +-- php/tests/Conformance/ConformanceTestCase.php | 14 ++++---- python/tests/conformance/support.py | 16 +++++----- scripts/validate-conformance-fixtures.mjs | 26 +++++++-------- src/__tests__/conformance/support.ts | 32 +++++++++---------- test/README.md | 30 ++++++++--------- .../classic/converts_special_characters.json | 2 +- .../excludes_ios_single_quote.json | 0 ...ute_components_by_relative_components.json | 2 +- ...uts_absolute_over_relative_components.json | 2 +- 10 files changed, 64 insertions(+), 64 deletions(-) rename test/{platform-exceptions => language-exceptions}/python/classic/converts_special_characters.json (76%) rename test/{platform-exceptions => language-exceptions}/python/hasSpecialCharacters/excludes_ios_single_quote.json (100%) rename test/{platform-exceptions/node => language-exceptions/ts}/vbml/layouts_absolute_components_by_relative_components.json (85%) rename test/{platform-exceptions/node => language-exceptions/ts}/vbml/layouts_absolute_over_relative_components.json (85%) diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 902a708..3753f85 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -27,7 +27,7 @@ jobs: - name: Validate Shared Fixtures run: yarn test:fixtures - node: + ts: needs: fixtures runs-on: ubuntu-latest steps: @@ -44,7 +44,7 @@ jobs: - name: Install Dependencies run: yarn install --frozen-lockfile - - name: Run Node Tests + - name: Run TypeScript Tests run: yarn test --runInBand python: diff --git a/php/tests/Conformance/ConformanceTestCase.php b/php/tests/Conformance/ConformanceTestCase.php index ae9adf2..7d97c58 100644 --- a/php/tests/Conformance/ConformanceTestCase.php +++ b/php/tests/Conformance/ConformanceTestCase.php @@ -8,7 +8,7 @@ abstract class ConformanceTestCase extends TestCase { - protected static function loadCases(string $suite, string $platform = 'php'): array + protected static function loadCases(string $suite, string $language = 'php'): array { $inputRoot = self::repoRoot() . "/test/input/{$suite}"; $expectedRoot = self::repoRoot() . "/test/expected/{$suite}"; @@ -29,7 +29,7 @@ protected static function loadCases(string $suite, string $platform = 'php'): ar $suite, $caseId, self::readJsonFile($expectedPath), - $platform + $language ); $cases[$caseId] = [[ @@ -106,17 +106,17 @@ private static function resolveExpected( string $suite, string $caseId, array $payload, - string $platform + string $language ): array { $exceptionPath = self::repoRoot() - . "/test/platform-exceptions/{$platform}/{$suite}/{$caseId}.json"; + . "/test/language-exceptions/{$language}/{$suite}/{$caseId}.json"; if (is_file($exceptionPath)) { $exception = self::readJsonFile($exceptionPath); $reason = trim((string)($exception['reason'] ?? '')); if ($reason === '') { throw new \RuntimeException( - "Platform exception \"{$suite}/{$caseId}\" is missing a reason." + "Language exception \"{$suite}/{$caseId}\" is missing a reason." ); } @@ -126,7 +126,7 @@ private static function resolveExpected( || array_key_exists('error', $exception) ) { throw new \RuntimeException( - "Platform exception \"{$suite}/{$caseId}\" cannot define skip with result or error." + "Language exception \"{$suite}/{$caseId}\" cannot define skip with result or error." ); } @@ -139,7 +139,7 @@ private static function resolveExpected( $hasError = array_key_exists('error', $exception); if ($hasResult === $hasError) { throw new \RuntimeException( - "Platform exception \"{$suite}/{$caseId}\" must define exactly one of result or error." + "Language exception \"{$suite}/{$caseId}\" must define exactly one of result or error." ); } diff --git a/python/tests/conformance/support.py b/python/tests/conformance/support.py index 3ec8bd7..9dc23b1 100644 --- a/python/tests/conformance/support.py +++ b/python/tests/conformance/support.py @@ -32,7 +32,7 @@ class ConformanceCase: _REPO_ROOT = Path(__file__).resolve().parents[3] _INPUT_ROOT = _REPO_ROOT / "test" / "input" _EXPECTED_ROOT = _REPO_ROOT / "test" / "expected" -_PLATFORM_EXCEPTION_ROOT = _REPO_ROOT / "test" / "platform-exceptions" +_LANGUAGE_EXCEPTION_ROOT = _REPO_ROOT / "test" / "language-exceptions" def _read_json(path: Path) -> Any: @@ -91,22 +91,22 @@ def _resolve_expected( suite: str, case_id: str, payload: dict[str, Any], - platform: str, + language: str, ) -> ResolvedExpected: - exception_path = _PLATFORM_EXCEPTION_ROOT / platform / suite / f"{case_id}.json" + exception_path = _LANGUAGE_EXCEPTION_ROOT / language / suite / f"{case_id}.json" if exception_path.exists(): exception = _read_json(exception_path) reason = str(exception.get("reason", "")).strip() if not reason: raise ValueError( - f'Platform exception "{suite}/{case_id}" is missing a reason.' + f'Language exception "{suite}/{case_id}" is missing a reason.' ) if exception.get("skip"): if "result" in exception or "error" in exception: raise ValueError( - f'Platform exception "{suite}/{case_id}" cannot define ' + f'Language exception "{suite}/{case_id}" cannot define ' "skip with result or error." ) @@ -116,7 +116,7 @@ def _resolve_expected( has_error = "error" in exception if has_result == has_error: raise ValueError( - f'Platform exception "{suite}/{case_id}" must define exactly ' + f'Language exception "{suite}/{case_id}" must define exactly ' "one of result or error." ) @@ -139,7 +139,7 @@ def _resolve_expected( ) -def load_cases(suite: str, platform: str = "python") -> list[ConformanceCase]: +def load_cases(suite: str, language: str = "python") -> list[ConformanceCase]: """Load all cases for a suite.""" input_root = _INPUT_ROOT / suite @@ -161,7 +161,7 @@ def load_cases(suite: str, platform: str = "python") -> list[ConformanceCase]: ConformanceCase( id=case_id, input_data=input_data, - expected=_resolve_expected(suite, case_id, expected_payload, platform), + expected=_resolve_expected(suite, case_id, expected_payload, language), ) ) diff --git a/scripts/validate-conformance-fixtures.mjs b/scripts/validate-conformance-fixtures.mjs index 1b351ba..316b67c 100644 --- a/scripts/validate-conformance-fixtures.mjs +++ b/scripts/validate-conformance-fixtures.mjs @@ -4,15 +4,15 @@ * The script treats `test/input//.json` and * `test/expected//.json` as the canonical shared cases and * verifies that every case has both halves. It also validates the shape of - * expected fixtures and platform-specific exceptions in - * `test/platform-exceptions///.json`. + * expected fixtures and language-specific exceptions in + * `test/language-exceptions///.json`. * * In practice this catches: * - missing input/expected pairs * - invalid JSON * - malformed expected error payloads - * - malformed platform exceptions - * - platform exceptions that do not map to a shared case + * - malformed language exceptions + * - language exceptions that do not map to a shared case */ import fs from "fs"; import path from "path"; @@ -24,7 +24,7 @@ const __dirname = path.dirname(__filename); const repoRoot = path.resolve(__dirname, ".."); const inputRoot = path.join(repoRoot, "test", "input"); const expectedRoot = path.join(repoRoot, "test", "expected"); -const platformExceptionRoot = path.join(repoRoot, "test", "platform-exceptions"); +const languageExceptionRoot = path.join(repoRoot, "test", "language-exceptions"); const errors = []; @@ -96,17 +96,17 @@ const validateExpectedFixture = (filePath, payload) => { } }; -const validatePlatformException = (filePath, payload, knownCases) => { - const relativePath = path.relative(platformExceptionRoot, filePath); +const validateLanguageException = (filePath, payload, knownCases) => { + const relativePath = path.relative(languageExceptionRoot, filePath); const fileLabel = path.relative(repoRoot, filePath); const segments = relativePath.split(path.sep); if (segments.length < 3) { - errors.push(`${fileLabel} must be stored as //.json.`); + errors.push(`${fileLabel} must be stored as //.json.`); return; } - const [platform, ...caseSegments] = segments; + const [, ...caseSegments] = segments; const caseId = caseSegments.join("/").replace(/\.json$/, ""); if (!knownCases.has(caseId)) { @@ -143,7 +143,7 @@ const validatePlatformException = (filePath, payload, knownCases) => { const inputFiles = walkJsonFiles(inputRoot); const expectedFiles = walkJsonFiles(expectedRoot); -const platformExceptionFiles = walkJsonFiles(platformExceptionRoot); +const languageExceptionFiles = walkJsonFiles(languageExceptionRoot); const inputCases = new Set(inputFiles.map((filePath) => toCaseId(inputRoot, filePath))); const expectedCases = new Set( @@ -173,10 +173,10 @@ const knownCases = new Set( [...inputCases].filter((caseId) => expectedCases.has(caseId)) ); -for (const exceptionFile of platformExceptionFiles) { +for (const exceptionFile of languageExceptionFiles) { const payload = readJsonFile(exceptionFile); if (payload !== null) { - validatePlatformException(exceptionFile, payload, knownCases); + validateLanguageException(exceptionFile, payload, knownCases); } } @@ -189,5 +189,5 @@ if (errors.length > 0) { } console.log( - `Validated ${knownCases.size} shared cases and ${platformExceptionFiles.length} platform exceptions.` + `Validated ${knownCases.size} shared cases and ${languageExceptionFiles.length} language exceptions.` ); diff --git a/src/__tests__/conformance/support.ts b/src/__tests__/conformance/support.ts index 5ca65d2..a63ed1e 100644 --- a/src/__tests__/conformance/support.ts +++ b/src/__tests__/conformance/support.ts @@ -5,7 +5,7 @@ interface ConformanceErrorExpectation { message: string; } -interface PlatformException { +interface LanguageException { reason: string; result?: TExpected; error?: ConformanceErrorExpectation; @@ -33,7 +33,7 @@ interface RunConformanceSuiteOptions { suiteName: string; suiteDir: string; run: (input: TInput) => TExpected; - platform?: string; + language?: string; } const readJsonFile = (filePath: string): T => @@ -79,15 +79,15 @@ const loadCases = (suiteDir: string): Array( - platform: string, +const loadLanguageException = ( + language: string, suiteDir: string, testId: string -): PlatformException | null => { +): LanguageException | null => { const exceptionPath = path.resolve( __dirname, - "../../../test/platform-exceptions", - platform, + "../../../test/language-exceptions", + language, suiteDir, `${testId}.json` ); @@ -96,16 +96,16 @@ const loadPlatformException = ( return null; } - return readJsonFile>(exceptionPath); + return readJsonFile>(exceptionPath); }; const resolveExpectation = ( - platform: string, + language: string, suiteDir: string, testCase: ConformanceCase ): ResolvedExpectation => { - const exception = loadPlatformException( - platform, + const exception = loadLanguageException( + language, suiteDir, testCase.id ); @@ -113,14 +113,14 @@ const resolveExpectation = ( if (exception) { if (!exception.reason.trim()) { throw new Error( - `Platform exception "${suiteDir}/${testCase.id}" is missing a reason for ${platform}.` + `Language exception "${suiteDir}/${testCase.id}" is missing a reason for ${language}.` ); } if (exception.skip) { if (exception.result !== undefined || exception.error !== undefined) { throw new Error( - `Platform exception "${suiteDir}/${testCase.id}" cannot define skip with result or error.` + `Language exception "${suiteDir}/${testCase.id}" cannot define skip with result or error.` ); } @@ -131,7 +131,7 @@ const resolveExpectation = ( if ((exception.result === undefined) === (exception.error === undefined)) { throw new Error( - `Platform exception "${suiteDir}/${testCase.id}" must define exactly one of result or error.` + `Language exception "${suiteDir}/${testCase.id}" must define exactly one of result or error.` ); } @@ -157,13 +157,13 @@ export const runConformanceSuite = ({ suiteName, suiteDir, run, - platform = "node", + language = "ts", }: RunConformanceSuiteOptions): void => { const cases = loadCases(suiteDir); describe(suiteName, () => { cases.forEach((testCase) => { - const resolved = resolveExpectation(platform, suiteDir, testCase); + const resolved = resolveExpectation(language, suiteDir, testCase); const testMethod = resolved.skip ? it.skip : it; const label = testCase.id; diff --git a/test/README.md b/test/README.md index 49d6f73..aabceab 100644 --- a/test/README.md +++ b/test/README.md @@ -1,21 +1,21 @@ # Shared Conformance Fixtures The `test/` directory is data-only. It defines the shared behavioral contract -for the Node, Python, and PHP implementations. +for the TypeScript, Python, and PHP implementations. Shared expectations in `test/expected` are the source of truth for every -platform. Platform-specific overrides in `test/platform-exceptions` document +language. Language-specific exceptions in `test/language-exceptions` document temporary or intentional drift from that shared contract. ## Layout - `test/input//.json` - `test/expected//.json` -- `test/platform-exceptions///.json` +- `test/language-exceptions///.json` The executable runners live outside of `test/`: -- Node: `src/__tests__/conformance` +- TypeScript: `src/__tests__/conformance` - Python: `python/tests/conformance` - PHP: `php/tests/Conformance` @@ -28,21 +28,21 @@ The executable runners live outside of `test/`: - `{ "result": ... }` - `{ "error": { "message": "..." } }` -## Platform Exception Rules +## Language Exception Rules -Use `test/platform-exceptions` only when a platform intentionally or currently +Use `test/language-exceptions` only when a language intentionally or currently behaves differently from the shared expectation. -- Every platform exception must include a non-empty `reason`. -- Every platform exception must define exactly one of `result`, `error`, or +- Every language exception must include a non-empty `reason`. +- Every language exception must define exactly one of `result`, `error`, or `skip`. - `skip` must be written as `{ "skip": true }`. -- Treat platform exceptions as temporary documentation of drift, not as an +- Treat language exceptions as temporary documentation of drift, not as an alternate test suite. ## When To Add Shared Cases -Prefer shared conformance coverage for supported behavior. Keep platform-native +Prefer shared conformance coverage for supported behavior. Keep language-native tests only when the contract is not cleanly expressible as input and expected output, such as deep-copy identity. @@ -51,15 +51,15 @@ output, such as deep-copy identity. 1. Add or update `test/input//.json`. 2. Add the matching `test/expected//.json` for the shared contract. -3. If any platform differs, add a matching file under - `test/platform-exceptions///.json` with a `reason`. -4. Remove the platform exception when that platform matches the shared contract. -5. Run the fixture validator and the affected platform suites. +3. If any language differs, add a matching file under + `test/language-exceptions///.json` with a `reason`. +4. Remove the language exception when that language matches the shared contract. +5. Run the fixture validator and the affected language suites. ## Commands - Validate shared fixtures: `yarn test:fixtures` -- Run Node tests: `yarn test --runInBand` +- Run TypeScript tests: `yarn test --runInBand` - Run Python conformance tests: `PYTHONPATH=python pytest python/tests/conformance -q` - Run PHP tests: `cd php && ./vendor/bin/phpunit` diff --git a/test/platform-exceptions/python/classic/converts_special_characters.json b/test/language-exceptions/python/classic/converts_special_characters.json similarity index 76% rename from test/platform-exceptions/python/classic/converts_special_characters.json rename to test/language-exceptions/python/classic/converts_special_characters.json index 58ef8d7..c59b829 100644 --- a/test/platform-exceptions/python/classic/converts_special_characters.json +++ b/test/language-exceptions/python/classic/converts_special_characters.json @@ -1,5 +1,5 @@ { - "reason": "Python classic wrapping diverges from Node when unsupported glyphs appear inside a long token sequence.", + "reason": "Python classic wrapping diverges from the TypeScript implementation when unsupported glyphs appear inside a long token sequence.", "result": [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], diff --git a/test/platform-exceptions/python/hasSpecialCharacters/excludes_ios_single_quote.json b/test/language-exceptions/python/hasSpecialCharacters/excludes_ios_single_quote.json similarity index 100% rename from test/platform-exceptions/python/hasSpecialCharacters/excludes_ios_single_quote.json rename to test/language-exceptions/python/hasSpecialCharacters/excludes_ios_single_quote.json diff --git a/test/platform-exceptions/node/vbml/layouts_absolute_components_by_relative_components.json b/test/language-exceptions/ts/vbml/layouts_absolute_components_by_relative_components.json similarity index 85% rename from test/platform-exceptions/node/vbml/layouts_absolute_components_by_relative_components.json rename to test/language-exceptions/ts/vbml/layouts_absolute_components_by_relative_components.json index 6f1d7e8..dc63806 100644 --- a/test/platform-exceptions/node/vbml/layouts_absolute_components_by_relative_components.json +++ b/test/language-exceptions/ts/vbml/layouts_absolute_components_by_relative_components.json @@ -1,5 +1,5 @@ { - "reason": "This absolute layout case uses truncated 6-column rows in the shared expected fixture, but the Node implementation returns a mixed-width board with 22-column leading rows and 6-column trailing rows.", + "reason": "This absolute layout case uses truncated 6-column rows in the shared expected fixture, but the TypeScript implementation returns a mixed-width board with 22-column leading rows and 6-column trailing rows.", "result": [ [1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], diff --git a/test/platform-exceptions/node/vbml/layouts_absolute_over_relative_components.json b/test/language-exceptions/ts/vbml/layouts_absolute_over_relative_components.json similarity index 85% rename from test/platform-exceptions/node/vbml/layouts_absolute_over_relative_components.json rename to test/language-exceptions/ts/vbml/layouts_absolute_over_relative_components.json index 26a8009..b93f4a6 100644 --- a/test/platform-exceptions/node/vbml/layouts_absolute_over_relative_components.json +++ b/test/language-exceptions/ts/vbml/layouts_absolute_over_relative_components.json @@ -1,5 +1,5 @@ { - "reason": "This absolute layout case uses truncated 6-column rows in the shared expected fixture, but the Node implementation returns a mixed-width board with 22-column leading rows and 6-column trailing rows.", + "reason": "This absolute layout case uses truncated 6-column rows in the shared expected fixture, but the TypeScript implementation returns a mixed-width board with 22-column leading rows and 6-column trailing rows.", "result": [ [4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],