From ea0e3ef391fdaed838d8efa06653649fa6aa67f3 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 14 Mar 2026 23:13:39 +0100 Subject: [PATCH 1/2] test(unit): add unit tests for env, math, colors, test_title, console_header, and doc --- tests/unit/colors_test.sh | 38 +++++++ tests/unit/console_header_test.sh | 119 ++++++++++++++++++++++ tests/unit/doc_test.sh | 53 ++++++++++ tests/unit/env_test.sh | 161 ++++++++++++++++++++++++++++++ tests/unit/fixtures/doc_sample.md | 17 ++++ tests/unit/math_test.sh | 74 ++++++++++++++ tests/unit/test_title_test.sh | 17 ++++ 7 files changed, 479 insertions(+) create mode 100644 tests/unit/colors_test.sh create mode 100644 tests/unit/console_header_test.sh create mode 100644 tests/unit/doc_test.sh create mode 100644 tests/unit/env_test.sh create mode 100644 tests/unit/fixtures/doc_sample.md create mode 100644 tests/unit/math_test.sh create mode 100644 tests/unit/test_title_test.sh diff --git a/tests/unit/colors_test.sh b/tests/unit/colors_test.sh new file mode 100644 index 00000000..00a25734 --- /dev/null +++ b/tests/unit/colors_test.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2155 + +function test_sgr_with_no_arguments_returns_reset() { + local result + result=$(bashunit::sgr) + + assert_equals $'\e[0m' "$result" +} + +function test_sgr_with_single_code() { + local result + result=$(bashunit::sgr 31) + + assert_equals $'\e[31m' "$result" +} + +function test_sgr_with_multiple_codes() { + local result + result=$(bashunit::sgr 1 31) + + assert_equals $'\e[1;31m' "$result" +} + +function test_sgr_with_three_codes() { + local result + result=$(bashunit::sgr 1 4 32) + + assert_equals $'\e[1;4;32m' "$result" +} + +function test_sgr_bold_code() { + local result + result=$(bashunit::sgr 1) + + assert_equals $'\e[1m' "$result" +} diff --git a/tests/unit/console_header_test.sh b/tests/unit/console_header_test.sh new file mode 100644 index 00000000..64c3fcae --- /dev/null +++ b/tests/unit/console_header_test.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2155 + +function test_print_help_contains_usage() { + local output + output=$(bashunit::console_header::print_help) + + assert_contains "Usage:" "$output" + assert_contains "bashunit" "$output" +} + +function test_print_help_contains_commands() { + local output + output=$(bashunit::console_header::print_help) + + assert_contains "Commands:" "$output" + assert_contains "test" "$output" + assert_contains "bench" "$output" + assert_contains "assert" "$output" + assert_contains "doc" "$output" + assert_contains "init" "$output" + assert_contains "learn" "$output" + assert_contains "watch" "$output" + assert_contains "upgrade" "$output" +} + +function test_print_help_contains_examples() { + local output + output=$(bashunit::console_header::print_help) + + assert_contains "Examples:" "$output" +} + +function test_print_test_help_contains_options() { + local output + output=$(bashunit::console_header::print_test_help) + + assert_contains "Options:" "$output" + assert_contains "--filter" "$output" + assert_contains "--parallel" "$output" + assert_contains "--verbose" "$output" + assert_contains "Coverage:" "$output" +} + +function test_print_bench_help_contains_usage() { + local output + output=$(bashunit::console_header::print_bench_help) + + assert_contains "Usage: bashunit bench" "$output" + assert_contains "Examples:" "$output" +} + +function test_print_doc_help_contains_usage() { + local output + output=$(bashunit::console_header::print_doc_help) + + assert_contains "Usage: bashunit doc" "$output" + assert_contains "filter" "$output" +} + +function test_print_init_help_contains_usage() { + local output + output=$(bashunit::console_header::print_init_help) + + assert_contains "Usage: bashunit init" "$output" + assert_contains "bootstrap.sh" "$output" +} + +function test_print_learn_help_contains_lessons() { + local output + output=$(bashunit::console_header::print_learn_help) + + assert_contains "Usage: bashunit learn" "$output" + assert_contains "tutorial" "$output" +} + +function test_print_upgrade_help_contains_usage() { + local output + output=$(bashunit::console_header::print_upgrade_help) + + assert_contains "Usage: bashunit upgrade" "$output" +} + +function test_print_assert_help_contains_examples() { + local output + output=$(bashunit::console_header::print_assert_help) + + assert_contains "Usage: bashunit assert" "$output" + assert_contains "equals" "$output" +} + +function test_print_watch_help_contains_requirements() { + local output + output=$(bashunit::console_header::print_watch_help) + + assert_contains "Usage: bashunit watch" "$output" + assert_contains "fswatch" "$output" + assert_contains "inotifywait" "$output" +} + +function test_print_version_with_env_returns_empty_when_header_disabled() { + local original="$BASHUNIT_SHOW_HEADER" + export BASHUNIT_SHOW_HEADER="false" + + local output + output=$(bashunit::console_header::print_version_with_env "") + + assert_empty "$output" + + export BASHUNIT_SHOW_HEADER="$original" +} + +function test_print_version_shows_version_string() { + local output + output=$(bashunit::console_header::print_version "" "dummy_file.sh") + + assert_contains "$BASHUNIT_VERSION" "$output" +} diff --git a/tests/unit/doc_test.sh b/tests/unit/doc_test.sh new file mode 100644 index 00000000..399da26b --- /dev/null +++ b/tests/unit/doc_test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2155 + +function test_print_asserts_outputs_assertion_names() { + bashunit::mock bashunit::doc::get_embedded_docs \ + cat ./tests/unit/fixtures/doc_sample.md + + local output + output=$(bashunit::doc::print_asserts) + + assert_contains "assert_equals" "$output" + assert_contains "assert_contains" "$output" +} + +function test_print_asserts_with_filter_matches() { + bashunit::mock bashunit::doc::get_embedded_docs \ + cat ./tests/unit/fixtures/doc_sample.md + + local output + output=$(bashunit::doc::print_asserts "contains") + + assert_contains "assert_contains" "$output" + assert_not_contains "assert_equals" "$output" +} + +function test_print_asserts_with_no_matching_filter() { + bashunit::mock bashunit::doc::get_embedded_docs \ + cat ./tests/unit/fixtures/doc_sample.md + + local output + output=$(bashunit::doc::print_asserts "nonexistent") + + assert_empty "$output" +} + +function test_print_asserts_strips_markdown_links() { + bashunit::mock bashunit::doc::get_embedded_docs \ + cat ./tests/unit/fixtures/doc_sample.md + + local output + output=$(bashunit::doc::print_asserts "contains") + + assert_not_contains "[" "$output" + assert_not_contains "]" "$output" +} + +function test_get_embedded_docs_returns_content() { + local output + output=$(bashunit::doc::get_embedded_docs) + + assert_not_empty "$output" +} diff --git a/tests/unit/env_test.sh b/tests/unit/env_test.sh new file mode 100644 index 00000000..7c060470 --- /dev/null +++ b/tests/unit/env_test.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2155 + +# @data_provider provide_boolean_flags_true +function test_env_flag_returns_success_when_true() { + local var_name="$1" + local fn_name="$2" + + local original_value="${!var_name}" + eval "export $var_name=true" + + "$fn_name" + assert_successful_code "$?" + + eval "export $var_name='$original_value'" +} + +function provide_boolean_flags_true() { + bashunit::data_set "BASHUNIT_PARALLEL_RUN" "bashunit::env::is_parallel_run_enabled" + bashunit::data_set "BASHUNIT_SHOW_HEADER" "bashunit::env::is_show_header_enabled" + bashunit::data_set "BASHUNIT_HEADER_ASCII_ART" "bashunit::env::is_header_ascii_art_enabled" + bashunit::data_set "BASHUNIT_SIMPLE_OUTPUT" "bashunit::env::is_simple_output_enabled" + bashunit::data_set "BASHUNIT_STOP_ON_FAILURE" "bashunit::env::is_stop_on_failure_enabled" + bashunit::data_set "BASHUNIT_SHOW_EXECUTION_TIME" "bashunit::env::is_show_execution_time_enabled" + bashunit::data_set "BASHUNIT_INTERNAL_LOG" "bashunit::env::is_internal_log_enabled" + bashunit::data_set "BASHUNIT_VERBOSE" "bashunit::env::is_verbose_enabled" + bashunit::data_set "BASHUNIT_BENCH_MODE" "bashunit::env::is_bench_mode_enabled" + bashunit::data_set "BASHUNIT_NO_OUTPUT" "bashunit::env::is_no_output_enabled" + bashunit::data_set "BASHUNIT_SHOW_SKIPPED" "bashunit::env::is_show_skipped_enabled" + bashunit::data_set "BASHUNIT_SHOW_INCOMPLETE" "bashunit::env::is_show_incomplete_enabled" + bashunit::data_set "BASHUNIT_STRICT_MODE" "bashunit::env::is_strict_mode_enabled" + bashunit::data_set "BASHUNIT_STOP_ON_ASSERTION_FAILURE" "bashunit::env::is_stop_on_assertion_failure_enabled" + bashunit::data_set "BASHUNIT_SKIP_ENV_FILE" "bashunit::env::is_skip_env_file_enabled" + bashunit::data_set "BASHUNIT_LOGIN_SHELL" "bashunit::env::is_login_shell_enabled" + bashunit::data_set "BASHUNIT_FAILURES_ONLY" "bashunit::env::is_failures_only_enabled" + bashunit::data_set "BASHUNIT_SHOW_OUTPUT_ON_FAILURE" "bashunit::env::is_show_output_on_failure_enabled" + bashunit::data_set "BASHUNIT_NO_PROGRESS" "bashunit::env::is_no_progress_enabled" + bashunit::data_set "BASHUNIT_NO_COLOR" "bashunit::env::is_no_color_enabled" + bashunit::data_set "BASHUNIT_COVERAGE" "bashunit::env::is_coverage_enabled" +} + +# @data_provider provide_boolean_flags_false +function test_env_flag_returns_failure_when_false() { + local var_name="$1" + local fn_name="$2" + + local original_value="${!var_name}" + eval "export $var_name=false" + + if "$fn_name"; then + eval "export $var_name='$original_value'" + fail "Expected $fn_name to return failure when $var_name=false" + return + fi + + eval "export $var_name='$original_value'" +} + +function provide_boolean_flags_false() { + bashunit::data_set "BASHUNIT_PARALLEL_RUN" "bashunit::env::is_parallel_run_enabled" + bashunit::data_set "BASHUNIT_SHOW_HEADER" "bashunit::env::is_show_header_enabled" + bashunit::data_set "BASHUNIT_SIMPLE_OUTPUT" "bashunit::env::is_simple_output_enabled" + bashunit::data_set "BASHUNIT_STOP_ON_FAILURE" "bashunit::env::is_stop_on_failure_enabled" + bashunit::data_set "BASHUNIT_VERBOSE" "bashunit::env::is_verbose_enabled" + bashunit::data_set "BASHUNIT_NO_OUTPUT" "bashunit::env::is_no_output_enabled" + bashunit::data_set "BASHUNIT_STRICT_MODE" "bashunit::env::is_strict_mode_enabled" + bashunit::data_set "BASHUNIT_NO_COLOR" "bashunit::env::is_no_color_enabled" + bashunit::data_set "BASHUNIT_COVERAGE" "bashunit::env::is_coverage_enabled" +} + +function test_is_dev_mode_enabled_when_dev_log_set() { + local original="$BASHUNIT_DEV_LOG" + export BASHUNIT_DEV_LOG="/tmp/dev.log" + + bashunit::env::is_dev_mode_enabled + assert_successful_code "$?" + + export BASHUNIT_DEV_LOG="$original" +} + +function test_is_dev_mode_disabled_when_dev_log_empty() { + local original="$BASHUNIT_DEV_LOG" + export BASHUNIT_DEV_LOG="" + + if bashunit::env::is_dev_mode_enabled; then + export BASHUNIT_DEV_LOG="$original" + fail "Expected is_dev_mode_enabled to return failure when BASHUNIT_DEV_LOG is empty" + return + fi + + export BASHUNIT_DEV_LOG="$original" +} + +function test_is_tap_output_enabled_when_format_is_tap() { + local original="$BASHUNIT_OUTPUT_FORMAT" + export BASHUNIT_OUTPUT_FORMAT="tap" + + bashunit::env::is_tap_output_enabled + assert_successful_code "$?" + + export BASHUNIT_OUTPUT_FORMAT="$original" +} + +function test_is_tap_output_disabled_when_format_is_not_tap() { + local original="$BASHUNIT_OUTPUT_FORMAT" + export BASHUNIT_OUTPUT_FORMAT="" + + if bashunit::env::is_tap_output_enabled; then + export BASHUNIT_OUTPUT_FORMAT="$original" + fail "Expected is_tap_output_enabled to return failure when format is empty" + return + fi + + export BASHUNIT_OUTPUT_FORMAT="$original" +} + +function test_active_internet_connection_returns_failure_when_no_network() { + local original="${BASHUNIT_NO_NETWORK:-}" + export BASHUNIT_NO_NETWORK="true" + + if bashunit::env::active_internet_connection; then + export BASHUNIT_NO_NETWORK="$original" + fail "Expected active_internet_connection to fail when BASHUNIT_NO_NETWORK=true" + return + fi + + export BASHUNIT_NO_NETWORK="$original" +} + +function test_find_terminal_width_returns_a_number() { + local result + result=$(bashunit::env::find_terminal_width) + + assert_matches "^[0-9]+$" "$result" +} + +function test_find_terminal_width_fallback_returns_100() { + bashunit::mock tput true + bashunit::mock stty true + + local result + result=$(bashunit::env::find_terminal_width) + + assert_equals "100" "$result" +} + +function test_print_verbose_outputs_env_var_names() { + local original="$BASHUNIT_VERBOSE" + export BASHUNIT_VERBOSE="true" + + local output + output=$(bashunit::env::print_verbose) + + assert_contains "BASHUNIT_DEFAULT_PATH" "$output" + assert_contains "BASHUNIT_PARALLEL_RUN" "$output" + assert_contains "BASHUNIT_VERBOSE" "$output" + assert_contains "BASHUNIT_COVERAGE" "$output" + + export BASHUNIT_VERBOSE="$original" +} diff --git a/tests/unit/fixtures/doc_sample.md b/tests/unit/fixtures/doc_sample.md new file mode 100644 index 00000000..f426c3e5 --- /dev/null +++ b/tests/unit/fixtures/doc_sample.md @@ -0,0 +1,17 @@ +## assert_equals + +Check if two values are equal. + +::: code-group + +```bash +assert_equals "expected" "actual" +``` + +## assert_contains + +Check if a string [contains](#assert-contains) a substring. + +```bash +assert_contains "needle" "haystack" +``` diff --git a/tests/unit/math_test.sh b/tests/unit/math_test.sh new file mode 100644 index 00000000..a921c481 --- /dev/null +++ b/tests/unit/math_test.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2155 + +function test_calculate_integer_addition() { + local result + result=$(bashunit::math::calculate "2 + 3") + + assert_equals "5" "$result" +} + +function test_calculate_integer_subtraction() { + local result + result=$(bashunit::math::calculate "10 - 4") + + assert_equals "6" "$result" +} + +function test_calculate_integer_multiplication() { + local result + result=$(bashunit::math::calculate "3 * 7") + + assert_equals "21" "$result" +} + +function test_calculate_integer_division() { + local result + result=$(bashunit::math::calculate "10 / 2") + + assert_equals "5" "$result" +} + +function test_calculate_zero_result() { + local result + result=$(bashunit::math::calculate "0 + 0") + + assert_equals "0" "$result" +} + +function test_calculate_with_bc_for_decimal() { + if ! bashunit::dependencies::has_bc; then + bashunit::skip "bc not available" + return + fi + + local result + result=$(bashunit::math::calculate "1.5 + 2.5") + + assert_equals "4.0" "$result" +} + +function test_calculate_fallback_to_awk_for_decimal() { + if ! bashunit::dependencies::has_awk; then + bashunit::skip "awk not available" + return + fi + + bashunit::mock bashunit::dependencies::has_bc false + + local result + result=$(bashunit::math::calculate "1.5 + 2.5") + + assert_equals "4" "$result" +} + +function test_calculate_fallback_to_bash_arithmetic_for_decimal() { + bashunit::mock bashunit::dependencies::has_bc false + bashunit::mock bashunit::dependencies::has_awk false + + local result + result=$(bashunit::math::calculate "10.5 + 20.3") + + assert_equals "30" "$result" +} diff --git a/tests/unit/test_title_test.sh b/tests/unit/test_title_test.sh new file mode 100644 index 00000000..aae6857d --- /dev/null +++ b/tests/unit/test_title_test.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +function test_set_test_title_delegates_to_state() { + bashunit::spy bashunit::state::set_test_title + + bashunit::set_test_title "my custom title" + + assert_have_been_called_with bashunit::state::set_test_title "my custom title" +} + +function test_set_test_title_delegates_empty_string() { + bashunit::spy bashunit::state::set_test_title + + bashunit::set_test_title "" + + assert_have_been_called bashunit::state::set_test_title +} From bf677d4a6c397bd58b67a5e96fc151b5556aefb3 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 14 Mar 2026 23:17:16 +0100 Subject: [PATCH 2/2] docs: update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda0be98..1b9ee4e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ - Add duration assertions: `assert_duration`, `assert_duration_less_than`, `assert_duration_greater_than` - Add `--tag` and `--exclude-tag` CLI flags for filtering tests by `# @tag` annotations +### Tests +- Add unit tests for `env.sh`, `math.sh`, `colors.sh`, `test_title.sh`, `console_header.sh`, and `doc.sh` + ### Changed - Optimize Claude Code config for agentic efficiency: trim rules/skills, add shellcheck hook, path-scoped rule loading - Split Windows CI test jobs into parallel chunks to avoid timeouts