__VSCODE_LARAVEL_START_OUTPUT__[{"name":"Unit","files":[{"path":"tests\/Unit\/ExampleTest.php","tests":[{"name":"test_that_true_is_true","eventName":"test_that_true_is_true","line":12}],"name":"ExampleTest","directories":[]},{"path":"tests\/Unit\/Automation\/SendWindowTest.php","tests":[{"name":"a moment inside the window is left as it is","eventName":"__pest_evaluable_a_moment_inside_the_window_is_left_as_it_is","line":23},{"name":"a send that comes due at 23:00 moves to 09:00 of the next allowed day","eventName":"__pest_evaluable_a_send_that_comes_due_at_23_00_moves_to_09_00_of_the_next_allowed_day","line":30},{"name":"the end of a range is exclusive and the start is inclusive","eventName":"__pest_evaluable_the_end_of_a_range_is_exclusive_and_the_start_is_inclusive","line":36},{"name":"before opening the same day opens later that day","eventName":"__pest_evaluable_before_opening_the_same_day_opens_later_that_day","line":43},{"name":"thursday afternoon skips the closed friday and reopens on saturday","eventName":"__pest_evaluable_thursday_afternoon_skips_the_closed_friday_and_reopens_on_saturday","line":47},{"name":"thursday has its own shorter hours","eventName":"__pest_evaluable_thursday_has_its_own_shorter_hours","line":53},{"name":"several ranges in one day are all honoured","eventName":"__pest_evaluable_several_ranges_in_one_day_are_all_honoured","line":58},{"name":"touching or overlapping ranges are merged","eventName":"__pest_evaluable_touching_or_overlapping_ranges_are_merged","line":67},{"name":"the window is evaluated in its own timezone, not the caller's","eventName":"__pest_evaluable_the_window_is_evaluated_in_its_own_timezone__not_the_caller_s","line":73},{"name":"a window in another timezone follows that timezone's clock","eventName":"__pest_evaluable_a_window_in_another_timezone_follows_that_timezone_s_clock","line":83},{"name":"the fallback timezone is used when the config names none","eventName":"__pest_evaluable_the_fallback_timezone_is_used_when_the_config_names_none","line":91},{"name":"an always-open window never defers","eventName":"__pest_evaluable_an_always_open_window_never_defers","line":97},{"name":"a range may end at 24:00","eventName":"__pest_evaluable_a_range_may_end_at_24_00","line":104},{"name":"a config is invalid when it has no open time, a bad time, an inverted range or overlaps","eventName":"__pest_evaluable_a_config_is_invalid_when_it_has_no_open_time__a_bad_time__an_inverted_range_or_overlaps","line":111},{"name":"a valid config has no errors and round-trips through toArray","eventName":"__pest_evaluable_a_valid_config_has_no_errors_and_round_trips_through_toArray","line":129}],"name":"SendWindowTest","directories":["Automation"]},{"path":"tests\/Unit\/Automation\/VariableRendererTest.php","tests":[{"name":"qualified and short variables are filled from the target","eventName":"__pest_evaluable_qualified_and_short_variables_are_filled_from_the_target","line":26},{"name":"a target with no first name is addressed by the full name, never by a guessed split","eventName":"__pest_evaluable_a_target_with_no_first_name_is_addressed_by_the_full_name__never_by_a_guessed_split","line":37},{"name":"a fallback is used when the value is empty, and an empty fallback blanks it on purpose","eventName":"__pest_evaluable_a_fallback_is_used_when_the_value_is_empty__and_an_empty_fallback_blanks_it_on_purpose","line":46},{"name":"a value that is present wins over its fallback","eventName":"__pest_evaluable_a_value_that_is_present_wins_over_its_fallback","line":56},{"name":"an empty value with no fallback renders blank and is reported as missing","eventName":"__pest_evaluable_an_empty_value_with_no_fallback_renders_blank_and_is_reported_as_missing","line":62},{"name":"a token that is not a variable is left as written and reported, never replaced","eventName":"__pest_evaluable_a_token_that_is_not_a_variable_is_left_as_written_and_reported__never_replaced","line":70},{"name":"names are case-insensitive and whitespace inside the braces is tolerated","eventName":"__pest_evaluable_names_are_case_insensitive_and_whitespace_inside_the_braces_is_tolerated","line":78},{"name":"validation lists every unknown variable in Persian and passes a clean template","eventName":"__pest_evaluable_validation_lists_every_unknown_variable_in_Persian_and_passes_a_clean_template","line":84},{"name":"text without any variable is returned untouched, braces that are not tokens included","eventName":"__pest_evaluable_text_without_any_variable_is_returned_untouched__braces_that_are_not_tokens_included","line":90},{"name":"rendering with no target at all leaves every target variable empty instead of failing","eventName":"__pest_evaluable_rendering_with_no_target_at_all_leaves_every_target_variable_empty_instead_of_failing","line":96},{"name":"sample rendering fills every registered variable so a preview is never blank","eventName":"__pest_evaluable_sample_rendering_fills_every_registered_variable_so_a_preview_is_never_blank","line":102},{"name":"a new family of variables is one registered provider, with no change to the renderer","eventName":"__pest_evaluable_a_new_family_of_variables_is_one_registered_provider__with_no_change_to_the_renderer","line":109},{"name":"the catalog groups qualified names by family for the variable picker","eventName":"__pest_evaluable_the_catalog_groups_qualified_names_by_family_for_the_variable_picker","line":143},{"name":"segments: a persian message is counted as unicode (70 then 67 per part), latin as gsm (160 then 153)","eventName":"__pest_evaluable_segments__a_persian_message_is_counted_as_unicode__70_then_67_per_part___latin_as_gsm__160_then_153_","line":151},{"name":"segments: one persian letter makes the whole message unicode, and GSM extension characters cost two","eventName":"__pest_evaluable_segments__one_persian_letter_makes_the_whole_message_unicode__and_GSM_extension_characters_cost_two","line":163}],"name":"VariableRendererTest","directories":["Automation"]},{"path":"tests\/Unit\/TargetSupportTest.php","tests":[{"name":"phone numbers are normalised from every shape a spreadsheet produces","eventName":"__pest_evaluable_phone_numbers_are_normalised_from_every_shape_a_spreadsheet_produces","line":8},{"name":"provinces resolve to one canonical name however they are typed","eventName":"__pest_evaluable_provinces_resolve_to_one_canonical_name_however_they_are_typed","line":27},{"name":"the spreadsheet layout is matched by header name, in any order, in Persian or English","eventName":"__pest_evaluable_the_spreadsheet_layout_is_matched_by_header_name__in_any_order__in_Persian_or_English","line":42},{"name":"a sheet without a mobile column is rejected with the expected format spelled out","eventName":"__pest_evaluable_a_sheet_without_a_mobile_column_is_rejected_with_the_expected_format_spelled_out","line":57},{"name":"a blank display name is composed from first and last name, a given one is never overwritten","eventName":"__pest_evaluable_a_blank_display_name_is_composed_from_first_and_last_name__a_given_one_is_never_overwritten","line":61},{"name":"a \"name\" column beside a \"last name\" column is read as first name, unless it already is the full name","eventName":"__pest_evaluable_a__name__column_beside_a__last_name__column_is_read_as_first_name__unless_it_already_is_the_full_name","line":70},{"name":"first and last name columns are found by their Persian and English headers","eventName":"__pest_evaluable_first_and_last_name_columns_are_found_by_their_Persian_and_English_headers","line":80}],"name":"TargetSupportTest","directories":[]}]},{"name":"Feature","files":[{"path":"tests\/Feature\/Auth\/AuthenticationTest.php","tests":[{"name":"test_login_screen_can_be_rendered","eventName":"test_login_screen_can_be_rendered","line":14},{"name":"test_users_can_authenticate_using_the_login_screen","eventName":"test_users_can_authenticate_using_the_login_screen","line":23},{"name":"test_users_can_not_authenticate_with_invalid_password","eventName":"test_users_can_not_authenticate_with_invalid_password","line":40},{"name":"test_login_validation_messages_are_localized","eventName":"test_login_validation_messages_are_localized","line":58},{"name":"test_navigation_menu_can_be_rendered","eventName":"test_navigation_menu_can_be_rendered","line":73},{"name":"test_users_can_logout","eventName":"test_users_can_logout","line":91}],"name":"AuthenticationTest","directories":["Auth"]},{"path":"tests\/Feature\/Auth\/EmailVerificationTest.php","tests":[{"name":"test_email_verification_screen_can_be_rendered","eventName":"test_email_verification_screen_can_be_rendered","line":16},{"name":"test_email_can_be_verified","eventName":"test_email_can_be_verified","line":27},{"name":"test_email_is_not_verified_with_invalid_hash","eventName":"test_email_is_not_verified_with_invalid_hash","line":46}],"name":"EmailVerificationTest","directories":["Auth"]},{"path":"tests\/Feature\/Auth\/PasswordConfirmationTest.php","tests":[{"name":"test_confirm_password_screen_can_be_rendered","eventName":"test_confirm_password_screen_can_be_rendered","line":14},{"name":"test_password_can_be_confirmed","eventName":"test_password_can_be_confirmed","line":25},{"name":"test_password_is_not_confirmed_with_invalid_password","eventName":"test_password_is_not_confirmed_with_invalid_password","line":41}],"name":"PasswordConfirmationTest","directories":["Auth"]},{"path":"tests\/Feature\/Auth\/PasswordResetTest.php","tests":[{"name":"test_reset_password_link_screen_can_be_rendered","eventName":"test_reset_password_link_screen_can_be_rendered","line":16},{"name":"test_reset_password_link_can_be_requested","eventName":"test_reset_password_link_can_be_requested","line":27},{"name":"test_reset_password_screen_can_be_rendered","eventName":"test_reset_password_screen_can_be_rendered","line":42},{"name":"test_password_can_be_reset_with_valid_token","eventName":"test_password_can_be_reset_with_valid_token","line":63}],"name":"PasswordResetTest","directories":["Auth"]},{"path":"tests\/Feature\/Auth\/PasswordUpdateTest.php","tests":[{"name":"test_password_can_be_updated","eventName":"test_password_can_be_updated","line":15},{"name":"test_correct_password_must_be_provided_to_update_password","eventName":"test_correct_password_must_be_provided_to_update_password","line":34}],"name":"PasswordUpdateTest","directories":["Auth"]},{"path":"tests\/Feature\/Auth\/RegistrationTest.php","tests":[{"name":"test_registration_screen_can_be_rendered","eventName":"test_registration_screen_can_be_rendered","line":13},{"name":"test_new_users_can_register","eventName":"test_new_users_can_register","line":22}],"name":"RegistrationTest","directories":["Auth"]},{"path":"tests\/Feature\/ExampleTest.php","tests":[{"name":"test_the_application_returns_a_successful_response","eventName":"test_the_application_returns_a_successful_response","line":13}],"name":"ExampleTest","directories":[]},{"path":"tests\/Feature\/ProfileTest.php","tests":[{"name":"test_profile_page_is_displayed","eventName":"test_profile_page_is_displayed","line":14},{"name":"test_profile_information_can_be_updated","eventName":"test_profile_information_can_be_updated","line":28},{"name":"test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged","eventName":"test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged","line":50},{"name":"test_user_can_delete_their_account","eventName":"test_user_can_delete_their_account","line":68},{"name":"test_correct_password_must_be_provided_to_delete_account","eventName":"test_correct_password_must_be_provided_to_delete_account","line":88}],"name":"ProfileTest","directories":[]},{"path":"tests\/Feature\/Activities\/ActivityListEditTest.php","tests":[{"name":"a user can edit an activity from the activities list","eventName":"__pest_evaluable_a_user_can_edit_an_activity_from_the_activities_list","line":9},{"name":"editing due_at to a new moment resets reminded_at so the reminder can fire again","eventName":"__pest_evaluable_editing_due__at_to_a_new_moment_resets_reminded__at_so_the_reminder_can_fire_again","line":36},{"name":"editing an activity without changing due_at leaves reminded_at untouched","eventName":"__pest_evaluable_editing_an_activity_without_changing_due__at_leaves_reminded__at_untouched","line":59},{"name":"checking \"done\" in the edit form completes the activity on save","eventName":"__pest_evaluable_checking__done__in_the_edit_form_completes_the_activity_on_save","line":84},{"name":"unchecking \"done\" on an already-completed activity reopens it","eventName":"__pest_evaluable_unchecking__done__on_an_already_completed_activity_reopens_it","line":105},{"name":"creating a new activity after save reopens the edit modal on the newly created one","eventName":"__pest_evaluable_creating_a_new_activity_after_save_reopens_the_edit_modal_on_the_newly_created_one","line":127},{"name":"the edit query param auto-opens that activity, e.g. from the dashboard quick actions","eventName":"__pest_evaluable_the_edit_query_param_auto_opens_that_activity__e_g__from_the_dashboard_quick_actions","line":147},{"name":"a user without update permission cannot open the edit form","eventName":"__pest_evaluable_a_user_without_update_permission_cannot_open_the_edit_form","line":163}],"name":"ActivityListEditTest","directories":["Activities"]},{"path":"tests\/Feature\/Activities\/ActivityListFilterTest.php","tests":[{"name":"search filters activities by subject","eventName":"__pest_evaluable_search_filters_activities_by_subject","line":8},{"name":"search filters activities by customer name","eventName":"__pest_evaluable_search_filters_activities_by_customer_name","line":27},{"name":"due date range filters out activities outside the window","eventName":"__pest_evaluable_due_date_range_filters_out_activities_outside_the_window","line":48},{"name":"status filter shows only pending or only completed activities","eventName":"__pest_evaluable_status_filter_shows_only_pending_or_only_completed_activities","line":70},{"name":"reset filters clears search status and date range","eventName":"__pest_evaluable_reset_filters_clears_search_status_and_date_range","line":92}],"name":"ActivityListFilterTest","directories":["Activities"]},{"path":"tests\/Feature\/Activities\/ActivityNotifyTeamTest.php","tests":[{"name":"activities:remind-due texts every active team member when notify_team is set","eventName":"__pest_evaluable_activities_remind_due_texts_every_active_team_member_when_notify__team_is_set","line":25},{"name":"activities:remind-due only texts the assignee when notify_team is not set","eventName":"__pest_evaluable_activities_remind_due_only_texts_the_assignee_when_notify__team_is_not_set","line":53},{"name":"activities:remind-overdue texts every active team member when notify_team is set","eventName":"__pest_evaluable_activities_remind_overdue_texts_every_active_team_member_when_notify__team_is_set","line":76}],"name":"ActivityNotifyTeamTest","directories":["Activities"]},{"path":"tests\/Feature\/Activities\/ActivityReminderTest.php","tests":[{"name":"a due pending activity notifies its assignee and is marked as reminded","eventName":"__pest_evaluable_a_due_pending_activity_notifies_its_assignee_and_is_marked_as_reminded","line":14},{"name":"a future or already-reminded or completed activity is not notified again","eventName":"__pest_evaluable_a_future_or_already_reminded_or_completed_activity_is_not_notified_again","line":36},{"name":"reminders are sent across multiple workspaces in the same run","eventName":"__pest_evaluable_reminders_are_sent_across_multiple_workspaces_in_the_same_run","line":65},{"name":"a due activity left behind by a soft-deleted workspace does not block reminders for other workspaces","eventName":"__pest_evaluable_a_due_activity_left_behind_by_a_soft_deleted_workspace_does_not_block_reminders_for_other_workspaces","line":94}],"name":"ActivityReminderTest","directories":["Activities"]},{"path":"tests\/Feature\/Activities\/ActivityTest.php","tests":[{"name":"creating an activity updates the customer last_activity_at","eventName":"__pest_evaluable_creating_an_activity_updates_the_customer_last__activity__at","line":9},{"name":"creating an activity defaults assigned_to to the acting user so it shows up in their activity list","eventName":"__pest_evaluable_creating_an_activity_defaults_assigned__to_to_the_acting_user_so_it_shows_up_in_their_activity_list","line":26},{"name":"completing an activity sets status, completed_at, and result","eventName":"__pest_evaluable_completing_an_activity_sets_status__completed__at__and_result","line":43},{"name":"workspace A cannot see workspace B activities","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_activities","line":62}],"name":"ActivityTest","directories":["Activities"]},{"path":"tests\/Feature\/Admin\/CreateAccountTest.php","tests":[{"name":"the admin creates a user together with the workspace they own","eventName":"__pest_evaluable_the_admin_creates_a_user_together_with_the_workspace_they_own","line":16},{"name":"the new workspace gets the default pipeline so deals are usable","eventName":"__pest_evaluable_the_new_workspace_gets_the_default_pipeline_so_deals_are_usable","line":42},{"name":"the account is verified on creation so it can actually log in","eventName":"__pest_evaluable_the_account_is_verified_on_creation_so_it_can_actually_log_in","line":57},{"name":"a workspace name is optional and the user is left to onboarding","eventName":"__pest_evaluable_a_workspace_name_is_optional_and_the_user_is_left_to_onboarding","line":71},{"name":"a duplicate email is rejected","eventName":"__pest_evaluable_a_duplicate_email_is_rejected","line":87},{"name":"a malformed mobile is rejected","eventName":"__pest_evaluable_a_malformed_mobile_is_rejected","line":99},{"name":"nothing is written when creation fails validation","eventName":"__pest_evaluable_nothing_is_written_when_creation_fails_validation","line":110},{"name":"the create form is prefilled from a trial request","eventName":"__pest_evaluable_the_create_form_is_prefilled_from_a_trial_request","line":122},{"name":"the user list finds an account by email","eventName":"__pest_evaluable_the_user_list_finds_an_account_by_email","line":131}],"name":"CreateAccountTest","directories":["Admin"]},{"path":"tests\/Feature\/Admin\/EditAccountTest.php","tests":[{"name":"the admin can edit every field of another user","eventName":"__pest_evaluable_the_admin_can_edit_every_field_of_another_user","line":13},{"name":"leaving the password blank keeps the existing one","eventName":"__pest_evaluable_leaving_the_password_blank_keeps_the_existing_one","line":39},{"name":"editing to an email already used by someone else is rejected","eventName":"__pest_evaluable_editing_to_an_email_already_used_by_someone_else_is_rejected","line":51},{"name":"keeping the same email while editing other fields is not rejected as a duplicate","eventName":"__pest_evaluable_keeping_the_same_email_while_editing_other_fields_is_not_rejected_as_a_duplicate","line":65},{"name":"toggling status suspends an active user and reactivates a suspended one","eventName":"__pest_evaluable_toggling_status_suspends_an_active_user_and_reactivates_a_suspended_one","line":78},{"name":"an admin cannot disable their own account","eventName":"__pest_evaluable_an_admin_cannot_disable_their_own_account","line":88},{"name":"a suspended user cannot log in","eventName":"__pest_evaluable_a_suspended_user_cannot_log_in","line":94},{"name":"suspending an already logged-in user signs them out on their next workspace request","eventName":"__pest_evaluable_suspending_an_already_logged_in_user_signs_them_out_on_their_next_workspace_request","line":106}],"name":"EditAccountTest","directories":["Admin"]},{"path":"tests\/Feature\/Admin\/GrantPlatformAdminCommandTest.php","tests":[{"name":"the command grants platform admin to an existing user","eventName":"__pest_evaluable_the_command_grants_platform_admin_to_an_existing_user","line":5},{"name":"the command revokes it again","eventName":"__pest_evaluable_the_command_revokes_it_again","line":13},{"name":"the command fails on an unknown email rather than creating an account","eventName":"__pest_evaluable_the_command_fails_on_an_unknown_email_rather_than_creating_an_account","line":21}],"name":"GrantPlatformAdminCommandTest","directories":["Admin"]},{"path":"tests\/Feature\/Admin\/PlatformAdminAccessTest.php","tests":[{"name":"the admin panel is closed to guests","eventName":"__pest_evaluable_the_admin_panel_is_closed_to_guests","line":5},{"name":"an ordinary logged-in user cannot reach the admin panel","eventName":"__pest_evaluable_an_ordinary_logged_in_user_cannot_reach_the_admin_panel","line":9},{"name":"a platform admin reaches the panel without belonging to any workspace","eventName":"__pest_evaluable_a_platform_admin_reaches_the_panel_without_belonging_to_any_workspace","line":17},{"name":"platform admin is off by default","eventName":"__pest_evaluable_platform_admin_is_off_by_default","line":23},{"name":"the panel asks search engines to stay away","eventName":"__pest_evaluable_the_panel_asks_search_engines_to_stay_away","line":27},{"name":"a platform admin is offered a way into the admin panel from the workspace chrome","eventName":"__pest_evaluable_a_platform_admin_is_offered_a_way_into_the_admin_panel_from_the_workspace_chrome","line":35},{"name":"a workspace member who is not a platform admin is not shown the admin panel link","eventName":"__pest_evaluable_a_workspace_member_who_is_not_a_platform_admin_is_not_shown_the_admin_panel_link","line":50},{"name":"the admin panel link points at a route the admin gate actually admits","eventName":"__pest_evaluable_the_admin_panel_link_points_at_a_route_the_admin_gate_actually_admits","line":63},{"name":"the sidebar spells out the admin screens rather than hiding them behind one label","eventName":"__pest_evaluable_the_sidebar_spells_out_the_admin_screens_rather_than_hiding_them_behind_one_label","line":73},{"name":"the admin section of the sidebar is invisible to an ordinary member","eventName":"__pest_evaluable_the_admin_section_of_the_sidebar_is_invisible_to_an_ordinary_member","line":90}],"name":"PlatformAdminAccessTest","directories":["Admin"]},{"path":"tests\/Feature\/Admin\/TrialRequestsTest.php","tests":[{"name":"a request submitted from the public site shows up in the panel","eventName":"__pest_evaluable_a_request_submitted_from_the_public_site_shows_up_in_the_panel","line":26},{"name":"a new request arrives unhandled","eventName":"__pest_evaluable_a_new_request_arrives_unhandled","line":45},{"name":"the public form cannot set its own status","eventName":"__pest_evaluable_the_public_form_cannot_set_its_own_status","line":50},{"name":"the admin can move a request through its statuses","eventName":"__pest_evaluable_the_admin_can_move_a_request_through_its_statuses","line":61},{"name":"sending a request back to new clears the handled timestamp","eventName":"__pest_evaluable_sending_a_request_back_to_new_clears_the_handled_timestamp","line":74},{"name":"the list filters by status","eventName":"__pest_evaluable_the_list_filters_by_status","line":83},{"name":"the list searches across name, mobile, business and city","eventName":"__pest_evaluable_the_list_searches_across_name__mobile__business_and_city","line":94},{"name":"the unhandled counter reflects only new requests","eventName":"__pest_evaluable_the_unhandled_counter_reflects_only_new_requests","line":105}],"name":"TrialRequestsTest","directories":["Admin"]},{"path":"tests\/Feature\/Attachments\/AttachmentTest.php","tests":[{"name":"storing a file creates an attachment record and puts the file on the private disk","eventName":"__pest_evaluable_storing_a_file_creates_an_attachment_record_and_puts_the_file_on_the_private_disk","line":42},{"name":"customer attachment upload route stores a selected file","eventName":"__pest_evaluable_customer_attachment_upload_route_stores_a_selected_file","line":57},{"name":"a team-visibility attachment is downloadable by anyone who can view the deal","eventName":"__pest_evaluable_a_team_visibility_attachment_is_downloadable_by_anyone_who_can_view_the_deal","line":76},{"name":"a private attachment is not downloadable by a user without files.view_sensitive","eventName":"__pest_evaluable_a_private_attachment_is_not_downloadable_by_a_user_without_files_view__sensitive","line":88},{"name":"deleting an attachment removes both the file and the record","eventName":"__pest_evaluable_deleting_an_attachment_removes_both_the_file_and_the_record","line":104},{"name":"workspace A cannot resolve workspace B attachments through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_resolve_workspace_B_attachments_through_the_global_scope","line":118}],"name":"AttachmentTest","directories":["Attachments"]},{"path":"tests\/Feature\/Automation\/AutomationFoundationTest.php","tests":[{"name":"a datetime is stored as UTC and read back as the same instant in the application timezone","eventName":"__pest_evaluable_a_datetime_is_stored_as_UTC_and_read_back_as_the_same_instant_in_the_application_timezone","line":48},{"name":"an instant handed in as UTC is stored unchanged, not shifted again","eventName":"__pest_evaluable_an_instant_handed_in_as_UTC_is_stored_unchanged__not_shifted_again","line":65},{"name":"a plain string is read in the application timezone and null stays null","eventName":"__pest_evaluable_a_plain_string_is_read_in_the_application_timezone_and_null_stays_null","line":77},{"name":"a query bound through the clock finds the row the same instant was stored for","eventName":"__pest_evaluable_a_query_bound_through_the_clock_finds_the_row_the_same_instant_was_stored_for","line":88},{"name":"the day a counter belongs to is the calendar day in the workspace timezone","eventName":"__pest_evaluable_the_day_a_counter_belongs_to_is_the_calendar_day_in_the_workspace_timezone","line":99},{"name":"the workspace timezone falls back to the application one when unset or invalid","eventName":"__pest_evaluable_the_workspace_timezone_falls_back_to_the_application_one_when_unset_or_invalid","line":106},{"name":"automation settings start switched off with a private webhook token, one row per workspace","eventName":"__pest_evaluable_automation_settings_start_switched_off_with_a_private_webhook_token__one_row_per_workspace","line":117},{"name":"automation settings are never shared between workspaces","eventName":"__pest_evaluable_automation_settings_are_never_shared_between_workspaces","line":131},{"name":"automation permissions: the owner has all, a sales manager runs campaigns, a viewer only looks, others have none","eventName":"__pest_evaluable_automation_permissions__the_owner_has_all__a_sales_manager_runs_campaigns__a_viewer_only_looks__others_have_none","line":148}],"name":"AutomationFoundationTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/BlacklistTest.php","tests":[{"name":"a blacklisted number is blocked however it is written, and only while the entry is active","eventName":"__pest_evaluable_a_blacklisted_number_is_blocked_however_it_is_written__and_only_while_the_entry_is_active","line":40},{"name":"a whole list can be checked at once","eventName":"__pest_evaluable_a_whole_list_can_be_checked_at_once","line":59},{"name":"adding numbers stores them normalised with reason, description, source and who added them","eventName":"__pest_evaluable_adding_numbers_stores_them_normalised_with_reason__description__source_and_who_added_them","line":68},{"name":"a number already blacklisted is a duplicate, not a second row","eventName":"__pest_evaluable_a_number_already_blacklisted_is_a_duplicate__not_a_second_row","line":85},{"name":"adding a number that was removed earlier switches it back on instead of inserting it twice","eventName":"__pest_evaluable_adding_a_number_that_was_removed_earlier_switches_it_back_on_instead_of_inserting_it_twice","line":95},{"name":"removing keeps the row as history, records who and when, and is audited","eventName":"__pest_evaluable_removing_keeps_the_row_as_history__records_who_and_when__and_is_audited","line":116},{"name":"every number added by hand is audited with who added it","eventName":"__pest_evaluable_every_number_added_by_hand_is_audited_with_who_added_it","line":132},{"name":"a large batch is one summary audit row instead of one per number","eventName":"__pest_evaluable_a_large_batch_is_one_summary_audit_row_instead_of_one_per_number","line":140},{"name":"the same number can be blacklisted independently in two workspaces and never leaks between them","eventName":"__pest_evaluable_the_same_number_can_be_blacklisted_independently_in_two_workspaces_and_never_leaks_between_them","line":158},{"name":"only roles that manage the blacklist can add or remove","eventName":"__pest_evaluable_only_roles_that_manage_the_blacklist_can_add_or_remove","line":176},{"name":"a plain list of numbers in the first column imports without a header","eventName":"__pest_evaluable_a_plain_list_of_numbers_in_the_first_column_imports_without_a_header","line":188},{"name":"an excel file with headers imports reason and description, reading persian reasons and stored keys","eventName":"__pest_evaluable_an_excel_file_with_headers_imports_reason_and_description__reading_persian_reasons_and_stored_keys","line":200},{"name":"an import that already-removed numbers appear in reactivates them and counts it","eventName":"__pest_evaluable_an_import_that_already_removed_numbers_appear_in_reactivates_them_and_counts_it","line":217},{"name":"an import file with no number column or numbers is refused, and so is an unsupported type","eventName":"__pest_evaluable_an_import_file_with_no_number_column_or_numbers_is_refused__and_so_is_an_unsupported_type","line":230},{"name":"the export contains the blacklist and can be re-imported as it is","eventName":"__pest_evaluable_the_export_contains_the_blacklist_and_can_be_re_imported_as_it_is","line":239},{"name":"the export only contains the current workspace and needs permission to view","eventName":"__pest_evaluable_the_export_only_contains_the_current_workspace_and_needs_permission_to_view","line":255},{"name":"the page adds numbers from the modal and reports what happened","eventName":"__pest_evaluable_the_page_adds_numbers_from_the_modal_and_reports_what_happened","line":269},{"name":"the page refuses input that holds no valid number and adds nothing","eventName":"__pest_evaluable_the_page_refuses_input_that_holds_no_valid_number_and_adds_nothing","line":286},{"name":"the page removes and re-blocks a number","eventName":"__pest_evaluable_the_page_removes_and_re_blocks_a_number","line":295},{"name":"the list filters by status, reason and a number typed in persian digits","eventName":"__pest_evaluable_the_list_filters_by_status__reason_and_a_number_typed_in_persian_digits","line":307},{"name":"the page imports a file and reports the counts","eventName":"__pest_evaluable_the_page_imports_a_file_and_reports_the_counts","line":325},{"name":"a viewer can see the blacklist but not change it; a seller cannot open it","eventName":"__pest_evaluable_a_viewer_can_see_the_blacklist_but_not_change_it__a_seller_cannot_open_it","line":340},{"name":"the automation pages do not exist while automation is switched off, and the sidebar hides them","eventName":"__pest_evaluable_the_automation_pages_do_not_exist_while_automation_is_switched_off__and_the_sidebar_hides_them","line":359}],"name":"BlacklistTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/CampaignActionsTest.php","tests":[{"name":"a campaign is created as a draft pinned to the workflow's published version, and audited","eventName":"__pest_evaluable_a_campaign_is_created_as_a_draft_pinned_to_the_workflow_s_published_version__and_audited","line":31},{"name":"a campaign needs a published workflow and sane inputs","eventName":"__pest_evaluable_a_campaign_needs_a_published_workflow_and_sane_inputs","line":48},{"name":"a campaign edit changes its schedule and limits but cannot touch an ended campaign","eventName":"__pest_evaluable_a_campaign_edit_changes_its_schedule_and_limits_but_cannot_touch_an_ended_campaign","line":64},{"name":"start, pause, resume, end and cancel move the campaign and each is audited","eventName":"__pest_evaluable_start__pause__resume__end_and_cancel_move_the_campaign_and_each_is_audited","line":80},{"name":"ending or cancelling a campaign cancels everyone still in it","eventName":"__pest_evaluable_ending_or_cancelling_a_campaign_cancels_everyone_still_in_it","line":101},{"name":"a running campaign cannot be deleted; a finished one can, and its history stays","eventName":"__pest_evaluable_a_running_campaign_cannot_be_deleted__a_finished_one_can__and_its_history_stays","line":113},{"name":"campaign changes need the manage permission and a campaign of this workspace","eventName":"__pest_evaluable_campaign_changes_need_the_manage_permission_and_a_campaign_of_this_workspace","line":129},{"name":"pause and resume of a single target keep its place and schedule, and name who did it","eventName":"__pest_evaluable_pause_and_resume_of_a_single_target_keep_its_place_and_schedule__and_name_who_did_it","line":147},{"name":"removing a target takes it out for good but keeps the history, and it can be added again","eventName":"__pest_evaluable_removing_a_target_takes_it_out_for_good_but_keeps_the_history__and_it_can_be_added_again","line":168},{"name":"a finished journey restarts as a new round, and a second restart of a live one is refused","eventName":"__pest_evaluable_a_finished_journey_restarts_as_a_new_round__and_a_second_restart_of_a_live_one_is_refused","line":186},{"name":"a bulk action carries on past the ones that cannot change and reports why, ignoring ids from other campaigns","eventName":"__pest_evaluable_a_bulk_action_carries_on_past_the_ones_that_cannot_change_and_reports_why__ignoring_ids_from_other_campaigns","line":206},{"name":"the dashboard figures count executions, messages and today's use of the daily limit","eventName":"__pest_evaluable_the_dashboard_figures_count_executions__messages_and_today_s_use_of_the_daily_limit","line":225},{"name":"a target that reaches a \"stop when\" status is taken out before its next message","eventName":"__pest_evaluable_a_target_that_reaches_a__stop_when__status_is_taken_out_before_its_next_message","line":248},{"name":"deleting targets cancels their journeys right away","eventName":"__pest_evaluable_deleting_targets_cancels_their_journeys_right_away","line":267}],"name":"CampaignActionsTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/CampaignPagesTest.php","tests":[{"name":"the campaign pages need automation access, and only managers see the manage controls","eventName":"__pest_evaluable_the_campaign_pages_need_automation_access__and_only_managers_see_the_manage_controls","line":32},{"name":"a campaign of another workspace is not reachable","eventName":"__pest_evaluable_a_campaign_of_another_workspace_is_not_reachable","line":51},{"name":"the list shows each campaign with how many are in it, and filters by status and name","eventName":"__pest_evaluable_the_list_shows_each_campaign_with_how_many_are_in_it__and_filters_by_status_and_name","line":62},{"name":"the form creates a campaign from a published workflow with its limits and window","eventName":"__pest_evaluable_the_form_creates_a_campaign_from_a_published_workflow_with_its_limits_and_window","line":74},{"name":"the form refuses a missing workflow or name, and reports the reason","eventName":"__pest_evaluable_the_form_refuses_a_missing_workflow_or_name__and_reports_the_reason","line":93},{"name":"editing prefills the form, keeps the workflow fixed and saves the changes","eventName":"__pest_evaluable_editing_prefills_the_form__keeps_the_workflow_fixed_and_saves_the_changes","line":101},{"name":"the dashboard controls start, pause, resume and end a campaign","eventName":"__pest_evaluable_the_dashboard_controls_start__pause__resume_and_end_a_campaign","line":117},{"name":"the dashboard shows the counts of targets and messages","eventName":"__pest_evaluable_the_dashboard_shows_the_counts_of_targets_and_messages","line":135},{"name":"the targets page lists journeys, filters them and controls them one by one and in bulk","eventName":"__pest_evaluable_the_targets_page_lists_journeys__filters_them_and_controls_them_one_by_one_and_in_bulk","line":144},{"name":"the targets page search finds a target by name","eventName":"__pest_evaluable_the_targets_page_search_finds_a_target_by_name","line":164},{"name":"a viewer sees the journeys but has no way to change them","eventName":"__pest_evaluable_a_viewer_sees_the_journeys_but_has_no_way_to_change_them","line":175},{"name":"enrolling: choose, get the duplicate warning, decide, and add","eventName":"__pest_evaluable_enrolling__choose__get_the_duplicate_warning__decide__and_add","line":189},{"name":"\"add all\" answers every duplicate at once","eventName":"__pest_evaluable__add_all__answers_every_duplicate_at_once","line":215},{"name":"\"everything this filter matches\" reviews every matching target, not just the page","eventName":"__pest_evaluable__everything_this_filter_matches__reviews_every_matching_target__not_just_the_page","line":229},{"name":"going back from the review drops the draft batch, and the batch id cannot be changed from the browser","eventName":"__pest_evaluable_going_back_from_the_review_drops_the_draft_batch__and_the_batch_id_cannot_be_changed_from_the_browser","line":241},{"name":"the campaign log shows what happened, and by whom","eventName":"__pest_evaluable_the_campaign_log_shows_what_happened__and_by_whom","line":258},{"name":"the SMS log needs its own permission, filters by status and shows the provider answer","eventName":"__pest_evaluable_the_SMS_log_needs_its_own_permission__filters_by_status_and_shows_the_provider_answer","line":269},{"name":"deleting a finished campaign from the dashboard sends the person back to the list","eventName":"__pest_evaluable_deleting_a_finished_campaign_from_the_dashboard_sends_the_person_back_to_the_list","line":288}],"name":"CampaignPagesTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/CircuitBreakerTest.php","tests":[{"name":"when the panel is out of credit the first message closes the channel and the rest wait without being attempted","eventName":"__pest_evaluable_when_the_panel_is_out_of_credit_the_first_message_closes_the_channel_and_the_rest_wait_without_being_attempted","line":26},{"name":"the owner is told once about an outage, not once per message","eventName":"__pest_evaluable_the_owner_is_told_once_about_an_outage__not_once_per_message","line":46},{"name":"after the pause the next message is a trial: if the channel is back everything flows again","eventName":"__pest_evaluable_after_the_pause_the_next_message_is_a_trial__if_the_channel_is_back_everything_flows_again","line":58},{"name":"a channel that is still down after the pause closes again, without telling the owner again","eventName":"__pest_evaluable_a_channel_that_is_still_down_after_the_pause_closes_again__without_telling_the_owner_again","line":77},{"name":"a workspace with SMS switched off is treated the same way","eventName":"__pest_evaluable_a_workspace_with_SMS_switched_off_is_treated_the_same_way","line":99},{"name":"one workspace's closed channel does not touch another's","eventName":"__pest_evaluable_one_workspace_s_closed_channel_does_not_touch_another_s","line":111},{"name":"the banner shows the closed channel and lets a manager reopen it, leaving an audit trail","eventName":"__pest_evaluable_the_banner_shows_the_closed_channel_and_lets_a_manager_reopen_it__leaving_an_audit_trail","line":123},{"name":"a viewer sees the warning but cannot reopen the channel, and the banner is empty when all is well","eventName":"__pest_evaluable_a_viewer_sees_the_warning_but_cannot_reopen_the_channel__and_the_banner_is_empty_when_all_is_well","line":135},{"name":"a running campaign with no scheduler heartbeat is flagged, and the tick command clears the flag","eventName":"__pest_evaluable_a_running_campaign_with_no_scheduler_heartbeat_is_flagged__and_the_tick_command_clears_the_flag","line":149},{"name":"executions handed to the queue that nobody picks up are reported as a stuck worker","eventName":"__pest_evaluable_executions_handed_to_the_queue_that_nobody_picks_up_are_reported_as_a_stuck_worker","line":167},{"name":"the person who made a campaign is told when it reaches its end time on its own","eventName":"__pest_evaluable_the_person_who_made_a_campaign_is_told_when_it_reaches_its_end_time_on_its_own","line":183}],"name":"CircuitBreakerTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/DeliveryReportTest.php","tests":[{"name":"a delivery report moves the message to delivered, keeps the panel's words, and leaves the workflow alone","eventName":"__pest_evaluable_a_delivery_report_moves_the_message_to_delivered__keeps_the_panel_s_words__and_leaves_the_workflow_alone","line":21},{"name":"a late report can add history but never takes a message backwards","eventName":"__pest_evaluable_a_late_report_can_add_history_but_never_takes_a_message_backwards","line":38},{"name":"the same report twice is recorded once","eventName":"__pest_evaluable_the_same_report_twice_is_recorded_once","line":49},{"name":"one request may carry many reports; those matching nothing are ignored without changing the answer","eventName":"__pest_evaluable_one_request_may_carry_many_reports__those_matching_nothing_are_ignored_without_changing_the_answer","line":60},{"name":"every refusal looks the same: wrong token, unknown provider, automation off","eventName":"__pest_evaluable_every_refusal_looks_the_same__wrong_token__unknown_provider__automation_off","line":79},{"name":"another workspace's token cannot touch this workspace's messages","eventName":"__pest_evaluable_another_workspace_s_token_cannot_touch_this_workspace_s_messages","line":96},{"name":"an oversized or malformed body is refused or ignored, never obeyed","eventName":"__pest_evaluable_an_oversized_or_malformed_body_is_refused_or_ignored__never_obeyed","line":111}],"name":"DeliveryReportTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/EngineFlowTest.php","tests":[{"name":"a target walks the workflow step by step, and every step leaves a line in its timeline","eventName":"__pest_evaluable_a_target_walks_the_workflow_step_by_step__and_every_step_leaves_a_line_in_its_timeline","line":20},{"name":"the wait counts from the moment each target actually got the message, not from when the campaign began","eventName":"__pest_evaluable_the_wait_counts_from_the_moment_each_target_actually_got_the_message__not_from_when_the_campaign_began","line":59},{"name":"a wait can also run until an exact moment, and a moment already past does not wait at all","eventName":"__pest_evaluable_a_wait_can_also_run_until_an_exact_moment__and_a_moment_already_past_does_not_wait_at_all","line":73},{"name":"a condition sends each target down the branch its data selects","eventName":"__pest_evaluable_a_condition_sends_each_target_down_the_branch_its_data_selects","line":89},{"name":"a condition can look at how the previous message went","eventName":"__pest_evaluable_a_condition_can_look_at_how_the_previous_message_went","line":117},{"name":"a step switched off is skipped without doing anything","eventName":"__pest_evaluable_a_step_switched_off_is_skipped_without_doing_anything","line":138},{"name":"an end that restarts brings the target back to the first step after its delay, up to the round limit","eventName":"__pest_evaluable_an_end_that_restarts_brings_the_target_back_to_the_first_step_after_its_delay__up_to_the_round_limit","line":156},{"name":"an end that loops goes back to a chosen step and keeps what the run has learned","eventName":"__pest_evaluable_an_end_that_loops_goes_back_to_a_chosen_step_and_keeps_what_the_run_has_learned","line":180},{"name":"a stop end finishes the journey and says it was a stop","eventName":"__pest_evaluable_a_stop_end_finishes_the_journey_and_says_it_was_a_stop","line":202},{"name":"a message whose variable has no value is skipped and logged, unless the step says to send it blank","eventName":"__pest_evaluable_a_message_whose_variable_has_no_value_is_skipped_and_logged__unless_the_step_says_to_send_it_blank","line":212},{"name":"a target that was deleted mid-journey cancels its execution instead of failing it","eventName":"__pest_evaluable_a_target_that_was_deleted_mid_journey_cancels_its_execution_instead_of_failing_it","line":231},{"name":"an execution row can never be deleted, and its timeline is append-only","eventName":"__pest_evaluable_an_execution_row_can_never_be_deleted__and_its_timeline_is_append_only","line":246},{"name":"a subject can be live in a campaign only once, but may join again after it has finished","eventName":"__pest_evaluable_a_subject_can_be_live_in_a_campaign_only_once__but_may_join_again_after_it_has_finished","line":259},{"name":"an execution enrolled before its campaign starts waits as pending and becomes due when the campaign starts","eventName":"__pest_evaluable_an_execution_enrolled_before_its_campaign_starts_waits_as_pending_and_becomes_due_when_the_campaign_starts","line":273},{"name":"an execution cannot make a move the state machine does not allow","eventName":"__pest_evaluable_an_execution_cannot_make_a_move_the_state_machine_does_not_allow","line":293},{"name":"a change made from a stale copy of an execution is refused and writes nothing","eventName":"__pest_evaluable_a_change_made_from_a_stale_copy_of_an_execution_is_refused_and_writes_nothing","line":308}],"name":"EngineFlowTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/EngineSafetyTest.php","tests":[{"name":"handing the same claimed execution to two workers sends once: the second finds it already taken","eventName":"__pest_evaluable_handing_the_same_claimed_execution_to_two_workers_sends_once__the_second_finds_it_already_taken","line":36},{"name":"two schedulers ticking at once cannot claim the same execution twice","eventName":"__pest_evaluable_two_schedulers_ticking_at_once_cannot_claim_the_same_execution_twice","line":51},{"name":"a job with an outdated token does nothing","eventName":"__pest_evaluable_a_job_with_an_outdated_token_does_nothing","line":65},{"name":"a step that already has an accepted attempt is completed without sending again","eventName":"__pest_evaluable_a_step_that_already_has_an_accepted_attempt_is_completed_without_sending_again","line":77},{"name":"an attempt left \"sending\" by a dead worker is closed as unknown and not sent again","eventName":"__pest_evaluable_an_attempt_left__sending__by_a_dead_worker_is_closed_as_unknown_and_not_sent_again","line":90},{"name":"a worker that died mid-run is recovered by the reaper, and what it had in flight is still not resent","eventName":"__pest_evaluable_a_worker_that_died_mid_run_is_recovered_by_the_reaper__and_what_it_had_in_flight_is_still_not_resent","line":105},{"name":"a queued execution nobody picked up is handed back after the lease","eventName":"__pest_evaluable_a_queued_execution_nobody_picked_up_is_handed_back_after_the_lease","line":127},{"name":"a job that dies with an exception gives its execution straight back to the scheduler","eventName":"__pest_evaluable_a_job_that_dies_with_an_exception_gives_its_execution_straight_back_to_the_scheduler","line":140},{"name":"the queue never retries the job itself: a retry of something that may have sent is how duplicates happen","eventName":"__pest_evaluable_the_queue_never_retries_the_job_itself__a_retry_of_something_that_may_have_sent_is_how_duplicates_happen","line":160},{"name":"an attempt key can exist only once, at the database level","eventName":"__pest_evaluable_an_attempt_key_can_exist_only_once__at_the_database_level","line":167},{"name":"an sms log is never overwritten: request fields are fixed, the result can be filled once, status only moves forward","eventName":"__pest_evaluable_an_sms_log_is_never_overwritten__request_fields_are_fixed__the_result_can_be_filled_once__status_only_moves_forward","line":176},{"name":"an attempt cannot skip the ladder: sending cannot jump straight to delivered","eventName":"__pest_evaluable_an_attempt_cannot_skip_the_ladder__sending_cannot_jump_straight_to_delivered","line":203},{"name":"delivery news is kept as its own append-only rows beside the attempt","eventName":"__pest_evaluable_delivery_news_is_kept_as_its_own_append_only_rows_beside_the_attempt","line":217},{"name":"the daily counter hands out exactly the limit, gives a slot back on release, and releasing twice frees only one","eventName":"__pest_evaluable_the_daily_counter_hands_out_exactly_the_limit__gives_a_slot_back_on_release__and_releasing_twice_frees_only_one","line":230},{"name":"an sms log always carries the workspace of the run that wrote it, with two workspaces running in one tick","eventName":"__pest_evaluable_an_sms_log_always_carries_the_workspace_of_the_run_that_wrote_it__with_two_workspaces_running_in_one_tick","line":251},{"name":"publishing a new version never disturbs an execution already running on the old one","eventName":"__pest_evaluable_publishing_a_new_version_never_disturbs_an_execution_already_running_on_the_old_one","line":267}],"name":"EngineSafetyTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/EngineSendingTest.php","tests":[{"name":"500-style scenario: a daily limit spreads the sends over days, and every target keeps its own 30 day rhythm","eventName":"__pest_evaluable_500_style_scenario__a_daily_limit_spreads_the_sends_over_days__and_every_target_keeps_its_own_30_day_rhythm","line":47},{"name":"a limit that is full stops claiming, and what was left waiting keeps its place in line for tomorrow","eventName":"__pest_evaluable_a_limit_that_is_full_stops_claiming__and_what_was_left_waiting_keeps_its_place_in_line_for_tomorrow","line":86},{"name":"outside the send window nothing goes out, and the next run is moved to the moment the window opens","eventName":"__pest_evaluable_outside_the_send_window_nothing_goes_out__and_the_next_run_is_moved_to_the_moment_the_window_opens","line":115},{"name":"friday is closed and thursday afternoon too: the next opening is saturday morning","eventName":"__pest_evaluable_friday_is_closed_and_thursday_afternoon_too__the_next_opening_is_saturday_morning","line":141},{"name":"a backlog pushed to the next window keeps its original order, so the daily limit serves the oldest first","eventName":"__pest_evaluable_a_backlog_pushed_to_the_next_window_keeps_its_original_order__so_the_daily_limit_serves_the_oldest_first","line":156},{"name":"a failed send is retried after the interval, every attempt is its own log, and the log row of a failure is not rewritten","eventName":"__pest_evaluable_a_failed_send_is_retried_after_the_interval__every_attempt_is_its_own_log__and_the_log_row_of_a_failure_is_not_rewritten","line":178},{"name":"when retries run out the step's own policy decides","eventName":"__pest_evaluable_when_retries_run_out_the_step_s_own_policy_decides","line":213},{"name":"a step with a failure branch sends the target down it once retries are used up","eventName":"__pest_evaluable_a_step_with_a_failure_branch_sends_the_target_down_it_once_retries_are_used_up","line":234},{"name":"a message the panel refuses is not retried, however many retries the step allows","eventName":"__pest_evaluable_a_message_the_panel_refuses_is_not_retried__however_many_retries_the_step_allows","line":253},{"name":"when the outcome of a send is unknown it is never sent again: the journey carries on, or holds for a person","eventName":"__pest_evaluable_when_the_outcome_of_a_send_is_unknown_it_is_never_sent_again__the_journey_carries_on__or_holds_for_a_person","line":267},{"name":"an error thrown while talking to the provider is treated as an unknown outcome, not as a reason to send again","eventName":"__pest_evaluable_an_error_thrown_while_talking_to_the_provider_is_treated_as_an_unknown_outcome__not_as_a_reason_to_send_again","line":292},{"name":"running out of credit pauses the send for a while without using up a retry or the daily limit","eventName":"__pest_evaluable_running_out_of_credit_pauses_the_send_for_a_while_without_using_up_a_retry_or_the_daily_limit","line":307},{"name":"a workspace whose SMS is switched off holds its messages instead of failing them","eventName":"__pest_evaluable_a_workspace_whose_SMS_is_switched_off_holds_its_messages_instead_of_failing_them","line":328},{"name":"a blacklisted number is never sent to and the attempt is logged as blacklisted","eventName":"__pest_evaluable_a_blacklisted_number_is_never_sent_to_and_the_attempt_is_logged_as_blacklisted","line":342},{"name":"a number blacklisted after it joined stops receiving from that moment, and resumes if it is taken off again","eventName":"__pest_evaluable_a_number_blacklisted_after_it_joined_stops_receiving_from_that_moment__and_resumes_if_it_is_taken_off_again","line":360},{"name":"the blacklist of one workspace never blocks another, even for the same number","eventName":"__pest_evaluable_the_blacklist_of_one_workspace_never_blocks_another__even_for_the_same_number","line":385},{"name":"a workspace with automation switched off has nothing claimed","eventName":"__pest_evaluable_a_workspace_with_automation_switched_off_has_nothing_claimed","line":404},{"name":"pausing a campaign stops all sending and touches no execution; resuming continues exactly where it stopped","eventName":"__pest_evaluable_pausing_a_campaign_stops_all_sending_and_touches_no_execution__resuming_continues_exactly_where_it_stopped","line":416},{"name":"a campaign paused after its executions were queued still sends nothing; they go back untouched","eventName":"__pest_evaluable_a_campaign_paused_after_its_executions_were_queued_still_sends_nothing__they_go_back_untouched","line":435},{"name":"a campaign that reaches its end date cancels what is still in flight, and keeps the history","eventName":"__pest_evaluable_a_campaign_that_reaches_its_end_date_cancels_what_is_still_in_flight__and_keeps_the_history","line":453}],"name":"EngineSendingTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/EnrollmentTest.php","tests":[{"name":"the planner sorts targets into clean, already in this campaign, running elsewhere, blacklisted and invalid \u2014 without writing anything","eventName":"__pest_evaluable_the_planner_sorts_targets_into_clean__already_in_this_campaign__running_elsewhere__blacklisted_and_invalid_\u2014_without_writing_anything","line":36},{"name":"duplicate details name the other campaign, its workflow, the current step and the next send time","eventName":"__pest_evaluable_duplicate_details_name_the_other_campaign__its_workflow__the_current_step_and_the_next_send_time","line":59},{"name":"a duplicate that nobody decided about stops the whole commit and nothing is added","eventName":"__pest_evaluable_a_duplicate_that_nobody_decided_about_stops_the_whole_commit_and_nothing_is_added","line":74},{"name":"every duplicate is added or skipped exactly as decided \u2014 one answer for all, overridden per target","eventName":"__pest_evaluable_every_duplicate_is_added_or_skipped_exactly_as_decided_\u2014_one_answer_for_all__overridden_per_target","line":90},{"name":"\"add all\" as the default adds every duplicate","eventName":"__pest_evaluable__add_all__as_the_default_adds_every_duplicate","line":112},{"name":"a decision for a target that is not a duplicate is ignored, and a bad decision is refused","eventName":"__pest_evaluable_a_decision_for_a_target_that_is_not_a_duplicate_is_ignored__and_a_bad_decision_is_refused","line":127},{"name":"a target another campaign picks up after the preview is caught by the fresh check at commit","eventName":"__pest_evaluable_a_target_another_campaign_picks_up_after_the_preview_is_caught_by_the_fresh_check_at_commit","line":141},{"name":"a number blacklisted after the preview is not enrolled","eventName":"__pest_evaluable_a_number_blacklisted_after_the_preview_is_not_enrolled","line":157},{"name":"committing the same batch twice enrols each target once","eventName":"__pest_evaluable_committing_the_same_batch_twice_enrols_each_target_once","line":171},{"name":"targets already in the campaign are reported, not enrolled a second time","eventName":"__pest_evaluable_targets_already_in_the_campaign_are_reported__not_enrolled_a_second_time","line":185},{"name":"a large batch is committed by a background job, small ones inline","eventName":"__pest_evaluable_a_large_batch_is_committed_by_a_background_job__small_ones_inline","line":199},{"name":"a job that dies leaves its batch marked failed, not stuck committing","eventName":"__pest_evaluable_a_job_that_dies_leaves_its_batch_marked_failed__not_stuck_committing","line":219},{"name":"nothing can be added to an ended campaign, and an empty choice is refused","eventName":"__pest_evaluable_nothing_can_be_added_to_an_ended_campaign__and_an_empty_choice_is_refused","line":230},{"name":"a person who only sees their own targets cannot enrol ones they were not shown","eventName":"__pest_evaluable_a_person_who_only_sees_their_own_targets_cannot_enrol_ones_they_were_not_shown","line":243},{"name":"only someone who manages campaigns can enrol, and never into another workspace's campaign","eventName":"__pest_evaluable_only_someone_who_manages_campaigns_can_enrol__and_never_into_another_workspace_s_campaign","line":259}],"name":"EnrollmentTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/SmsTemplateTest.php","tests":[{"name":"a template is created with its text, who made it, and an audit row","eventName":"__pest_evaluable_a_template_is_created_with_its_text__who_made_it__and_an_audit_row","line":18},{"name":"a body using a variable nobody registered is refused with the reason, and nothing is saved","eventName":"__pest_evaluable_a_body_using_a_variable_nobody_registered_is_refused_with_the_reason__and_nothing_is_saved","line":30},{"name":"a template needs a name and a body, and its name must be unique in the workspace","eventName":"__pest_evaluable_a_template_needs_a_name_and_a_body__and_its_name_must_be_unique_in_the_workspace","line":37},{"name":"editing records the old and new text, and an unchanged save writes no audit row","eventName":"__pest_evaluable_editing_records_the_old_and_new_text__and_an_unchanged_save_writes_no_audit_row","line":49},{"name":"deleting soft-deletes the template and audits it; the name can be reused afterwards","eventName":"__pest_evaluable_deleting_soft_deletes_the_template_and_audits_it__the_name_can_be_reused_afterwards","line":64},{"name":"created, updated and deleted times are stored as UTC","eventName":"__pest_evaluable_created__updated_and_deleted_times_are_stored_as_UTC","line":76},{"name":"templates are never shared between workspaces","eventName":"__pest_evaluable_templates_are_never_shared_between_workspaces","line":91},{"name":"only roles that manage templates can save or delete them","eventName":"__pest_evaluable_only_roles_that_manage_templates_can_save_or_delete_them","line":105},{"name":"the page creates, edits and deletes templates","eventName":"__pest_evaluable_the_page_creates__edits_and_deletes_templates","line":117},{"name":"the page shows an unknown variable as an error on the text and does not save","eventName":"__pest_evaluable_the_page_shows_an_unknown_variable_as_an_error_on_the_text_and_does_not_save","line":142},{"name":"the live preview renders sample values, counts segments and lists unknown variables","eventName":"__pest_evaluable_the_live_preview_renders_sample_values__counts_segments_and_lists_unknown_variables","line":154},{"name":"the page lists templates with their segment count, and a viewer sees them without the edit controls","eventName":"__pest_evaluable_the_page_lists_templates_with_their_segment_count__and_a_viewer_sees_them_without_the_edit_controls","line":168}],"name":"SmsTemplateTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/TargetTimelineTest.php","tests":[{"name":"the timeline shows the campaigns a target is in now, and every message that went out with its text and the panel's answer","eventName":"__pest_evaluable_the_timeline_shows_the_campaigns_a_target_is_in_now__and_every_message_that_went_out_with_its_text_and_the_panel_s_answer","line":18},{"name":"a failed attempt and its retry both appear, with the retry time","eventName":"__pest_evaluable_a_failed_attempt_and_its_retry_both_appear__with_the_retry_time","line":32},{"name":"a manual pause names who did it","eventName":"__pest_evaluable_a_manual_pause_names_who_did_it","line":47},{"name":"a target that was never in a campaign says so, and the list of old events can be extended","eventName":"__pest_evaluable_a_target_that_was_never_in_a_campaign_says_so__and_the_list_of_old_events_can_be_extended","line":57},{"name":"the target page shows the timeline only when automation is on and the person may see campaigns","eventName":"__pest_evaluable_the_target_page_shows_the_timeline_only_when_automation_is_on_and_the_person_may_see_campaigns","line":79},{"name":"another workspace's target has no timeline here","eventName":"__pest_evaluable_another_workspace_s_target_has_no_timeline_here","line":100}],"name":"TargetTimelineTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/WebhookSettingsTest.php","tests":[{"name":"the delivery-report address is shown to the owner only while automation is on","eventName":"__pest_evaluable_the_delivery_report_address_is_shown_to_the_owner_only_while_automation_is_on","line":9},{"name":"a fresh address replaces the old one, which stops working, and the change is audited","eventName":"__pest_evaluable_a_fresh_address_replaces_the_old_one__which_stops_working__and_the_change_is_audited","line":20},{"name":"someone who cannot manage SMS cannot see or change the address","eventName":"__pest_evaluable_someone_who_cannot_manage_SMS_cannot_see_or_change_the_address","line":35}],"name":"WebhookSettingsTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/WorkflowActionsTest.php","tests":[{"name":"a blank workflow starts as an unpublished draft with an empty start-to-end graph, and its creation is audited","eventName":"__pest_evaluable_a_blank_workflow_starts_as_an_unpublished_draft_with_an_empty_start_to_end_graph__and_its_creation_is_audited","line":46},{"name":"a workflow needs a name that is not already used, and only people who manage workflows can create one","eventName":"__pest_evaluable_a_workflow_needs_a_name_that_is_not_already_used__and_only_people_who_manage_workflows_can_create_one","line":60},{"name":"a workflow made from a ready-made template gets its steps and defaults, with its own keys, and can be published as it is","eventName":"__pest_evaluable_a_workflow_made_from_a_ready_made_template_gets_its_steps_and_defaults__with_its_own_keys__and_can_be_published_as_it_is","line":76},{"name":"saving a draft stores the steps and the defaults, and the audit log keeps what each message said before and after","eventName":"__pest_evaluable_saving_a_draft_stores_the_steps_and_the_defaults__and_the_audit_log_keeps_what_each_message_said_before_and_after","line":93},{"name":"saving an unchanged draft writes no audit row, and an incomplete draft can still be kept","eventName":"__pest_evaluable_saving_an_unchanged_draft_writes_no_audit_row__and_an_incomplete_draft_can_still_be_kept","line":114},{"name":"the defaults of a version are validated","eventName":"__pest_evaluable_the_defaults_of_a_version_are_validated","line":131},{"name":"an empty limit and window clear the defaults","eventName":"__pest_evaluable_an_empty_limit_and_window_clear_the_defaults","line":145},{"name":"a published version cannot be edited: changes go into a new draft made from it","eventName":"__pest_evaluable_a_published_version_cannot_be_edited__changes_go_into_a_new_draft_made_from_it","line":154},{"name":"publishing is audited and refused with every problem when the graph is not valid","eventName":"__pest_evaluable_publishing_is_audited_and_refused_with_every_problem_when_the_graph_is_not_valid","line":179},{"name":"only a draft of a published workflow can be discarded, and the published version is untouched","eventName":"__pest_evaluable_only_a_draft_of_a_published_workflow_can_be_discarded__and_the_published_version_is_untouched","line":209},{"name":"a workflow in use by a campaign can not be archived until the campaign is over, and one that ever had a campaign cannot be deleted","eventName":"__pest_evaluable_a_workflow_in_use_by_a_campaign_can_not_be_archived_until_the_campaign_is_over__and_one_that_ever_had_a_campaign_cannot_be_deleted","line":227},{"name":"a workflow nobody ever used is deleted softly and audited","eventName":"__pest_evaluable_a_workflow_nobody_ever_used_is_deleted_softly_and_audited","line":248},{"name":"a workflow can be kept as a template of the workspace and used to make new ones","eventName":"__pest_evaluable_a_workflow_can_be_kept_as_a_template_of_the_workspace_and_used_to_make_new_ones","line":261},{"name":"a workflow with a shape templates cannot hold is refused with the reason","eventName":"__pest_evaluable_a_workflow_with_a_shape_templates_cannot_hold_is_refused_with_the_reason","line":288},{"name":"workflows and templates of one workspace are invisible and untouchable from another","eventName":"__pest_evaluable_workflows_and_templates_of_one_workspace_are_invisible_and_untouchable_from_another","line":301},{"name":"roles that only look at automation cannot change any workflow","eventName":"__pest_evaluable_roles_that_only_look_at_automation_cannot_change_any_workflow","line":324}],"name":"WorkflowActionsTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/WorkflowGraphTest.php","tests":[{"name":"a linear workflow of messages and waits is valid","eventName":"__pest_evaluable_a_linear_workflow_of_messages_and_waits_is_valid","line":52},{"name":"a workflow needs exactly one start and at least one end","eventName":"__pest_evaluable_a_workflow_needs_exactly_one_start_and_at_least_one_end","line":61},{"name":"every step must lead somewhere: an action, wait and start need a next step, a condition both branches, an end none","eventName":"__pest_evaluable_every_step_must_lead_somewhere__an_action__wait_and_start_need_a_next_step__a_condition_both_branches__an_end_none","line":69},{"name":"a port used twice, an unknown port and an edge to nowhere are refused","eventName":"__pest_evaluable_a_port_used_twice__an_unknown_port_and_an_edge_to_nowhere_are_refused","line":80},{"name":"an action may have a failure branch alongside its success path","eventName":"__pest_evaluable_an_action_may_have_a_failure_branch_alongside_its_success_path","line":90},{"name":"a step nothing can reach is reported","eventName":"__pest_evaluable_a_step_nothing_can_reach_is_reported","line":97},{"name":"an action must exist and its own settings are validated, including unknown message variables","eventName":"__pest_evaluable_an_action_must_exist_and_its_own_settings_are_validated__including_unknown_message_variables","line":105},{"name":"waits need a real duration or a real date","eventName":"__pest_evaluable_waits_need_a_real_duration_or_a_real_date","line":118},{"name":"conditions are checked for real fields, real operators and a value","eventName":"__pest_evaluable_conditions_are_checked_for_real_fields__real_operators_and_a_value","line":131},{"name":"a loop in the graph is only allowed if it passes through a timed wait","eventName":"__pest_evaluable_a_loop_in_the_graph_is_only_allowed_if_it_passes_through_a_timed_wait","line":144},{"name":"an end that loops or restarts needs a round limit, a delay of at least an hour and a real target","eventName":"__pest_evaluable_an_end_that_loops_or_restarts_needs_a_round_limit__a_delay_of_at_least_an_hour_and_a_real_target","line":167},{"name":"only the manual start is supported for now","eventName":"__pest_evaluable_only_the_manual_start_is_supported_for_now","line":196},{"name":"a new workflow starts as draft version 1 and cannot have two drafts","eventName":"__pest_evaluable_a_new_workflow_starts_as_draft_version_1_and_cannot_have_two_drafts","line":203},{"name":"publishing refuses an invalid graph and names the problem","eventName":"__pest_evaluable_publishing_refuses_an_invalid_graph_and_names_the_problem","line":216},{"name":"publishing fixes the version, marks which steps use the daily limit and makes it what new campaigns get","eventName":"__pest_evaluable_publishing_fixes_the_version__marks_which_steps_use_the_daily_limit_and_makes_it_what_new_campaigns_get","line":228},{"name":"a published version can never be edited, only a new draft started from it","eventName":"__pest_evaluable_a_published_version_can_never_be_edited__only_a_new_draft_started_from_it","line":241},{"name":"publishing a second version supersedes the first but keeps it","eventName":"__pest_evaluable_publishing_a_second_version_supersedes_the_first_but_keeps_it","line":265}],"name":"WorkflowGraphTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/WorkflowPagesTest.php","tests":[{"name":"the workflow list shows this workspace's workflows, filtered by search and status","eventName":"__pest_evaluable_the_workflow_list_shows_this_workspace_s_workflows__filtered_by_search_and_status","line":43},{"name":"another workspace never sees these workflows, and one it does not own is a 404","eventName":"__pest_evaluable_another_workspace_never_sees_these_workflows__and_one_it_does_not_own_is_a_404","line":60},{"name":"the pages do not exist while automation is off, and a seller cannot open them","eventName":"__pest_evaluable_the_pages_do_not_exist_while_automation_is_off__and_a_seller_cannot_open_them","line":71},{"name":"automation being switched off hides the workflow pages","eventName":"__pest_evaluable_automation_being_switched_off_hides_the_workflow_pages","line":83},{"name":"a new workflow is created from the modal, from a ready-made template, and opens in the builder","eventName":"__pest_evaluable_a_new_workflow_is_created_from_the_modal__from_a_ready_made_template__and_opens_in_the_builder","line":93},{"name":"creating a workflow needs an unused name, and a saved template shows up as a choice","eventName":"__pest_evaluable_creating_a_workflow_needs_an_unused_name__and_a_saved_template_shows_up_as_a_choice","line":108},{"name":"the viewer sees the list but has no way to create","eventName":"__pest_evaluable_the_viewer_sees_the_list_but_has_no_way_to_create","line":124},{"name":"the detail page lists versions, and shows what changed in a message between two of them","eventName":"__pest_evaluable_the_detail_page_lists_versions__and_shows_what_changed_in_a_message_between_two_of_them","line":136},{"name":"editing a published workflow makes a draft and opens the builder; archive, restore and delete follow the rules","eventName":"__pest_evaluable_editing_a_published_workflow_makes_a_draft_and_opens_the_builder__archive__restore_and_delete_follow_the_rules","line":156},{"name":"a workflow can be saved as a template from its page","eventName":"__pest_evaluable_a_workflow_can_be_saved_as_a_template_from_its_page","line":180},{"name":"the builder opens the draft and shows its steps, and a published version is shown read-only","eventName":"__pest_evaluable_the_builder_opens_the_draft_and_shows_its_steps__and_a_published_version_is_shown_read_only","line":195},{"name":"adding an sms step opens its editor, an empty message is refused, and applying it puts the step in the sequence","eventName":"__pest_evaluable_adding_an_sms_step_opens_its_editor__an_empty_message_is_refused__and_applying_it_puts_the_step_in_the_sequence","line":213},{"name":"the editor previews the message with sample values and flags an unknown variable","eventName":"__pest_evaluable_the_editor_previews_the_message_with_sample_values_and_flags_an_unknown_variable","line":239},{"name":"a saved sms template can be copied into a step","eventName":"__pest_evaluable_a_saved_sms_template_can_be_copied_into_a_step","line":247},{"name":"a wait is set from a preset, by hand, or to a date, and its numbers are validated","eventName":"__pest_evaluable_a_wait_is_set_from_a_preset__by_hand__or_to_a_date__and_its_numbers_are_validated","line":257},{"name":"a condition gets two branches, takes steps inside them, and only ever closes its list","eventName":"__pest_evaluable_a_condition_gets_two_branches__takes_steps_inside_them__and_only_ever_closes_its_list","line":274},{"name":"a condition can be rebuilt with a rule that needs no value, and rules can be added and removed","eventName":"__pest_evaluable_a_condition_can_be_rebuilt_with_a_rule_that_needs_no_value__and_rules_can_be_added_and_removed","line":299},{"name":"steps are removed, moved and switched off from their cards","eventName":"__pest_evaluable_steps_are_removed__moved_and_switched_off_from_their_cards","line":310},{"name":"an end can loop back to a step with a delay and a round limit, and a loop needs its target","eventName":"__pest_evaluable_an_end_can_loop_back_to_a_step_with_a_delay_and_a_round_limit__and_a_loop_needs_its_target","line":326},{"name":"saving the draft stores the steps and the sending defaults; an invalid default is reported and nothing is lost","eventName":"__pest_evaluable_saving_the_draft_stores_the_steps_and_the_sending_defaults__an_invalid_default_is_reported_and_nothing_is_lost","line":342},{"name":"the send window round-trips through the settings form, including a window with several ranges in a day","eventName":"__pest_evaluable_the_send_window_round_trips_through_the_settings_form__including_a_window_with_several_ranges_in_a_day","line":361},{"name":"publishing from the builder lists what is wrong, and once it is valid publishes and goes to the workflow page","eventName":"__pest_evaluable_publishing_from_the_builder_lists_what_is_wrong__and_once_it_is_valid_publishes_and_goes_to_the_workflow_page","line":385},{"name":"from a published version the builder can start a draft, and an unfinished draft can be discarded","eventName":"__pest_evaluable_from_a_published_version_the_builder_can_start_a_draft__and_an_unfinished_draft_can_be_discarded","line":412},{"name":"a specific version can be opened by number, read-only once it is published","eventName":"__pest_evaluable_a_specific_version_can_be_opened_by_number__read_only_once_it_is_published","line":426},{"name":"a workflow the step editor cannot draw is shown with the reasons and can not be edited from it","eventName":"__pest_evaluable_a_workflow_the_step_editor_cannot_draw_is_shown_with_the_reasons_and_can_not_be_edited_from_it","line":438},{"name":"a viewer sees the steps without any editing controls and can not change anything","eventName":"__pest_evaluable_a_viewer_sees_the_steps_without_any_editing_controls_and_can_not_change_anything","line":454},{"name":"the locked parts of the builder cannot be changed from the browser","eventName":"__pest_evaluable_the_locked_parts_of_the_builder_cannot_be_changed_from_the_browser","line":470},{"name":"the sidebar links to the workflow pages only for those who can see them, while automation is on","eventName":"__pest_evaluable_the_sidebar_links_to_the_workflow_pages_only_for_those_who_can_see_them__while_automation_is_on","line":479}],"name":"WorkflowPagesTest","directories":["Automation"]},{"path":"tests\/Feature\/Automation\/WorkflowSequenceTest.php","tests":[{"name":"an empty sequence compiles to start \u2192 end, and a filled one to a chain of edges in order","eventName":"__pest_evaluable_an_empty_sequence_compiles_to_start_\u2192_end__and_a_filled_one_to_a_chain_of_edges_in_order","line":36},{"name":"steps are inserted at the position asked, and every step keeps its own stable key","eventName":"__pest_evaluable_steps_are_inserted_at_the_position_asked__and_every_step_keeps_its_own_stable_key","line":55},{"name":"a condition closes its list: it may only be added last, nothing follows it, and it takes the end away","eventName":"__pest_evaluable_a_condition_closes_its_list__it_may_only_be_added_last__nothing_follows_it__and_it_takes_the_end_away","line":65},{"name":"a condition compiles to yes and no edges, each branch running to its own end","eventName":"__pest_evaluable_a_condition_compiles_to_yes_and_no_edges__each_branch_running_to_its_own_end","line":82},{"name":"compile then decompile gives back the same sequence, branches included","eventName":"__pest_evaluable_compile_then_decompile_gives_back_the_same_sequence__branches_included","line":95},{"name":"a graph the step list cannot draw is reported instead of being guessed at","eventName":"__pest_evaluable_a_graph_the_step_list_cannot_draw_is_reported_instead_of_being_guessed_at","line":106},{"name":"removing a step closes the gap; removing a condition drops its branches and restores an end","eventName":"__pest_evaluable_removing_a_step_closes_the_gap__removing_a_condition_drops_its_branches_and_restores_an_end","line":128},{"name":"steps move up and down, but a condition stays last and nothing jumps over it","eventName":"__pest_evaluable_steps_move_up_and_down__but_a_condition_stays_last_and_nothing_jumps_over_it","line":144},{"name":"a step can be updated and an end reconfigured, and a loop follows a step until that step is removed","eventName":"__pest_evaluable_a_step_can_be_updated_and_an_end_reconfigured__and_a_loop_follows_a_step_until_that_step_is_removed","line":162},{"name":"every step of a sequence is listed in reading order with the path to it","eventName":"__pest_evaluable_every_step_of_a_sequence_is_listed_in_reading_order_with_the_path_to_it","line":176},{"name":"rekeying gives a copy new keys everywhere and keeps a loop pointing at the same step","eventName":"__pest_evaluable_rekeying_gives_a_copy_new_keys_everywhere_and_keeps_a_loop_pointing_at_the_same_step","line":183},{"name":"a sequence with an empty message is a valid draft but is refused on publish-time validation, with the step named","eventName":"__pest_evaluable_a_sequence_with_an_empty_message_is_a_valid_draft_but_is_refused_on_publish_time_validation__with_the_step_named","line":196},{"name":"every ready-made template compiles to a valid workflow and can be used more than once","eventName":"__pest_evaluable_every_ready_made_template_compiles_to_a_valid_workflow_and_can_be_used_more_than_once","line":207},{"name":"no ready-made message addresses the recipient with a gendered title","eventName":"__pest_evaluable_no_ready_made_message_addresses_the_recipient_with_a_gendered_title","line":229},{"name":"steps are described in one Persian line for the cards","eventName":"__pest_evaluable_steps_are_described_in_one_Persian_line_for_the_cards","line":237},{"name":"comparing two versions says which steps were added, removed and changed, with the old and new message text","eventName":"__pest_evaluable_comparing_two_versions_says_which_steps_were_added__removed_and_changed__with_the_old_and_new_message_text","line":251},{"name":"a change to a step's settings or to whether it is enabled shows up in the comparison","eventName":"__pest_evaluable_a_change_to_a_step_s_settings_or_to_whether_it_is_enabled_shows_up_in_the_comparison","line":275}],"name":"WorkflowSequenceTest","directories":["Automation"]},{"path":"tests\/Feature\/Catalog\/CatalogCategoryTest.php","tests":[{"name":"the owner can create, update, and delete a catalog category","eventName":"__pest_evaluable_the_owner_can_create__update__and_delete_a_catalog_category","line":20},{"name":"a category cannot be set as its own parent","eventName":"__pest_evaluable_a_category_cannot_be_set_as_its_own_parent","line":40},{"name":"a seller cannot manage catalog categories","eventName":"__pest_evaluable_a_seller_cannot_manage_catalog_categories","line":50},{"name":"the categories page lists, creates, and deletes via the Livewire component","eventName":"__pest_evaluable_the_categories_page_lists__creates__and_deletes_via_the_Livewire_component","line":60}],"name":"CatalogCategoryTest","directories":["Catalog"]},{"path":"tests\/Feature\/Catalog\/CatalogItemTest.php","tests":[{"name":"creating a catalog item writes an initial version snapshot","eventName":"__pest_evaluable_creating_a_catalog_item_writes_an_initial_version_snapshot","line":37},{"name":"changing the price writes a new version and is audited","eventName":"__pest_evaluable_changing_the_price_writes_a_new_version_and_is_audited","line":53},{"name":"price change is blocked when allow_price_change is false","eventName":"__pest_evaluable_price_change_is_blocked_when_allow__price__change_is_false","line":68},{"name":"price change is blocked below the configured minimum price","eventName":"__pest_evaluable_price_change_is_blocked_below_the_configured_minimum_price","line":80},{"name":"commission rate must stay within 0 to 100","eventName":"__pest_evaluable_commission_rate_must_stay_within_0_to_100","line":94},{"name":"changing availability updates status and stock and writes a version","eventName":"__pest_evaluable_changing_availability_updates_status_and_stock_and_writes_a_version","line":106},{"name":"updating an item through the generic path refuses financial fields","eventName":"__pest_evaluable_updating_an_item_through_the_generic_path_refuses_financial_fields","line":122},{"name":"workspace A cannot see workspace B catalog items","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_catalog_items","line":137},{"name":"changing a catalog item price does not alter a previously created quotation item","eventName":"__pest_evaluable_changing_a_catalog_item_price_does_not_alter_a_previously_created_quotation_item","line":163}],"name":"CatalogItemTest","directories":["Catalog"]},{"path":"tests\/Feature\/Cms\/BistunHomepagePresetTest.php","tests":[{"name":"installing the preset builds the website, both languages, every section type and the page in order","eventName":"__pest_evaluable_installing_the_preset_builds_the_website__both_languages__every_section_type_and_the_page_in_order","line":68},{"name":"the hero carries the agreed copy in both languages and the seven need options with stable form values","eventName":"__pest_evaluable_the_hero_carries_the_agreed_copy_in_both_languages_and_the_seven_need_options_with_stable_form_values","line":94},{"name":"the trust wall lists the sixteen organisations from the brief, in both languages, with no logo invented","eventName":"__pest_evaluable_the_trust_wall_lists_the_sixteen_organisations_from_the_brief__in_both_languages__with_no_logo_invented","line":116},{"name":"the systems hub has all nine systems including VPMS, with Bismart the only featured one","eventName":"__pest_evaluable_the_systems_hub_has_all_nine_systems_including_VPMS__with_Bismart_the_only_featured_one","line":128},{"name":"every problem card links to a system page and the FAQ has all eight answers in both languages","eventName":"__pest_evaluable_every_problem_card_links_to_a_system_page_and_the_FAQ_has_all_eight_answers_in_both_languages","line":143},{"name":"no result, quote, logo, photo or article link is invented: those stay empty and testimonials are inactive","eventName":"__pest_evaluable_no_result__quote__logo__photo_or_article_link_is_invented__those_stay_empty_and_testimonials_are_inactive","line":158},{"name":"the contact group and the page SEO exist for both languages","eventName":"__pest_evaluable_the_contact_group_and_the_page_SEO_exist_for_both_languages","line":178},{"name":"published, the homepage is served by the public API with its active sections and both languages","eventName":"__pest_evaluable_published__the_homepage_is_served_by_the_public_API_with_its_active_sections_and_both_languages","line":193},{"name":"running it again is refused, replacing rebuilds the sections without duplicating section types","eventName":"__pest_evaluable_running_it_again_is_refused__replacing_rebuilds_the_sections_without_duplicating_section_types","line":207},{"name":"an existing section type with the same key but different fields is never written into","eventName":"__pest_evaluable_an_existing_section_type_with_the_same_key_but_different_fields_is_never_written_into","line":224},{"name":"a workspace that already has one website uses it and keeps its languages; several websites need an explicit choice","eventName":"__pest_evaluable_a_workspace_that_already_has_one_website_uses_it_and_keeps_its_languages__several_websites_need_an_explicit_choice","line":237},{"name":"the artisan command installs into a workspace found by name and reports failures plainly","eventName":"__pest_evaluable_the_artisan_command_installs_into_a_workspace_found_by_name_and_reports_failures_plainly","line":254},{"name":"every installed section opens in the admin page editor and saves back unchanged","eventName":"__pest_evaluable_every_installed_section_opens_in_the_admin_page_editor_and_saves_back_unchanged","line":274},{"name":"a site whose languages are named fa-IR and en_US is used as it is, without adding a second fa and en","eventName":"__pest_evaluable_a_site_whose_languages_are_named_fa_IR_and_en__US_is_used_as_it_is__without_adding_a_second_fa_and_en","line":299},{"name":"when the site already has a homepage, the error names it and how to replace its sections","eventName":"__pest_evaluable_when_the_site_already_has_a_homepage__the_error_names_it_and_how_to_replace_its_sections","line":316},{"name":"a dry run reports what would be installed but leaves the database untouched","eventName":"__pest_evaluable_a_dry_run_reports_what_would_be_installed_but_leaves_the_database_untouched","line":327},{"name":"--fresh removes the old website with everything under it and builds the new one, freeing the old key","eventName":"__pest_evaluable___fresh_removes_the_old_website_with_everything_under_it_and_builds_the_new_one__freeing_the_old_key","line":359},{"name":"--fresh asks first, and saying no changes nothing","eventName":"__pest_evaluable___fresh_asks_first__and_saying_no_changes_nothing","line":393},{"name":"--fresh is all or nothing: if the new install cannot be built the old site is still there","eventName":"__pest_evaluable___fresh_is_all_or_nothing__if_the_new_install_cannot_be_built_the_old_site_is_still_there","line":405},{"name":"--fresh needs a new website key and refuses --website","eventName":"__pest_evaluable___fresh_needs_a_new_website_key_and_refuses___website","line":421},{"name":"--fresh with --dry-run shows the outcome but deletes nothing","eventName":"__pest_evaluable___fresh_with___dry_run_shows_the_outcome_but_deletes_nothing","line":430}],"name":"BistunHomepagePresetTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/BistunSiteKeepTypesTest.php","tests":[{"name":"with the existing section types kept, not one type or field is created, added to or changed","eventName":"__pest_evaluable_with_the_existing_section_types_kept__not_one_type_or_field_is_created__added_to_or_changed","line":100},{"name":"a preset that needs a type the panel does not have stops before writing anything","eventName":"__pest_evaluable_a_preset_that_needs_a_type_the_panel_does_not_have_stops_before_writing_anything","line":116},{"name":"the home page is rearranged to the guide: hero and FAQ kept as entered, the rest replaced, both addresses right","eventName":"__pest_evaluable_the_home_page_is_rearranged_to_the_guide__hero_and_FAQ_kept_as_entered__the_rest_replaced__both_addresses_right","line":124},{"name":"the contact page gets its missing English translation and the guide sections","eventName":"__pest_evaluable_the_contact_page_gets_its_missing_English_translation_and_the_guide_sections","line":147},{"name":"content is fitted to the real fields: lists of strings become one-column rows, links stay inside the site","eventName":"__pest_evaluable_content_is_fitted_to_the_real_fields__lists_of_strings_become_one_column_rows__links_stay_inside_the_site","line":160},{"name":"the fields that cannot hold English are listed, so nothing is silently half-translated","eventName":"__pest_evaluable_the_fields_that_cannot_hold_English_are_listed__so_nothing_is_silently_half_translated","line":183},{"name":"the logo already in the media library is attached to its client by file name","eventName":"__pest_evaluable_the_logo_already_in_the_media_library_is_attached_to_its_client_by_file_name","line":194},{"name":"menus, the contact group and posts are filled while the products menu and existing values are left alone","eventName":"__pest_evaluable_menus__the_contact_group_and_posts_are_filled_while_the_products_menu_and_existing_values_are_left_alone","line":206}],"name":"BistunSiteKeepTypesTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/BistunSitePresetTest.php","tests":[{"name":"the guide's twelve pages are built in both languages with their own addresses and section order","eventName":"__pest_evaluable_the_guide_s_twelve_pages_are_built_in_both_languages_with_their_own_addresses_and_section_order","line":36},{"name":"section types are named exactly like the frontend library and use its camelCase field names","eventName":"__pest_evaluable_section_types_are_named_exactly_like_the_frontend_library_and_use_its_camelCase_field_names","line":60},{"name":"the public API serves every page in the shape the frontend components read, in both languages","eventName":"__pest_evaluable_the_public_API_serves_every_page_in_the_shape_the_frontend_components_read__in_both_languages","line":77},{"name":"nothing on an English page is left in Persian, so no demo text shows through","eventName":"__pest_evaluable_nothing_on_an_English_page_is_left_in_Persian__so_no_demo_text_shows_through","line":120},{"name":"the header and footer menus are trees whose links resolve to the pages, with English labels","eventName":"__pest_evaluable_the_header_and_footer_menus_are_trees_whose_links_resolve_to_the_pages__with_English_labels","line":137},{"name":"contact details are global content in both languages and the social group exists empty","eventName":"__pest_evaluable_contact_details_are_global_content_in_both_languages_and_the_social_group_exists_empty","line":156},{"name":"the four blog categories and ten draft posts are created with one address for both languages","eventName":"__pest_evaluable_the_four_blog_categories_and_ten_draft_posts_are_created_with_one_address_for_both_languages","line":171},{"name":"running it again is refused, and replacing rebuilds pages and menus without duplicating posts or types","eventName":"__pest_evaluable_running_it_again_is_refused__and_replacing_rebuilds_pages_and_menus_without_duplicating_posts_or_types","line":188},{"name":"list fields survive a round trip through the admin page editor","eventName":"__pest_evaluable_list_fields_survive_a_round_trip_through_the_admin_page_editor","line":204}],"name":"BistunSitePresetTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/BlogTest.php","tests":[{"name":"a category can be created with translations and a slug must be unique per language","eventName":"__pest_evaluable_a_category_can_be_created_with_translations_and_a_slug_must_be_unique_per_language","line":16},{"name":"a category with assigned posts cannot be deleted","eventName":"__pest_evaluable_a_category_with_assigned_posts_cannot_be_deleted","line":33},{"name":"a tag can be created and assigned to a post","eventName":"__pest_evaluable_a_tag_can_be_created_and_assigned_to_a_post","line":49},{"name":"a category or tag from a different website cannot be assigned to a post","eventName":"__pest_evaluable_a_category_or_tag_from_a_different_website_cannot_be_assigned_to_a_post","line":63},{"name":"a post slug must be unique within the website and language","eventName":"__pest_evaluable_a_post_slug_must_be_unique_within_the_website_and_language","line":77},{"name":"a post cannot be published without at least one translation","eventName":"__pest_evaluable_a_post_cannot_be_published_without_at_least_one_translation","line":89},{"name":"publishing a post sets published_at","eventName":"__pest_evaluable_publishing_a_post_sets_published__at","line":100},{"name":"updating a post replaces its translation content","eventName":"__pest_evaluable_updating_a_post_replaces_its_translation_content","line":113},{"name":"deleting a post is a soft delete","eventName":"__pest_evaluable_deleting_a_post_is_a_soft_delete","line":129},{"name":"workspace A cannot load workspace B posts or categories through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_load_workspace_B_posts_or_categories_through_the_global_scope","line":141},{"name":"the post edit page renders with translations, categories, and tags","eventName":"__pest_evaluable_the_post_edit_page_renders_with_translations__categories__and_tags","line":157},{"name":"the blog posts index page lists and creates via the Livewire component","eventName":"__pest_evaluable_the_blog_posts_index_page_lists_and_creates_via_the_Livewire_component","line":179}],"name":"BlogTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/ContentValueServiceTest.php","tests":[{"name":"a repeater field stores rows and per-row translatable\/non-translatable child values","eventName":"__pest_evaluable_a_repeater_field_stores_rows_and_per_row_translatable_non_translatable_child_values","line":11},{"name":"a group field treats its children exactly like top-level fields","eventName":"__pest_evaluable_a_group_field_treats_its_children_exactly_like_top_level_fields","line":48},{"name":"duplicating owner values copies every stored row to the new owner id","eventName":"__pest_evaluable_duplicating_owner_values_copies_every_stored_row_to_the_new_owner_id","line":76},{"name":"a url field accepts links inside the site, anchors, tel: and mailto:, but not a bare word or a protocol-relative address","eventName":"__pest_evaluable_a_url_field_accepts_links_inside_the_site__anchors__tel__and_mailto___but_not_a_bare_word_or_a_protocol_relative_address","line":98}],"name":"ContentValueServiceTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/FieldSchemaTest.php","tests":[{"name":"a section type can be created with a stable key and fields added to it","eventName":"__pest_evaluable_a_section_type_can_be_created_with_a_stable_key_and_fields_added_to_it","line":15},{"name":"a field key must be unique within the same schema and nesting level","eventName":"__pest_evaluable_a_field_key_must_be_unique_within_the_same_schema_and_nesting_level","line":30},{"name":"a repeater can have plain child fields but not a nested repeater or group","eventName":"__pest_evaluable_a_repeater_can_have_plain_child_fields_but_not_a_nested_repeater_or_group","line":42},{"name":"a select field requires at least one option","eventName":"__pest_evaluable_a_select_field_requires_at_least_one_option","line":61},{"name":"validation_rules are sanitized against a whitelist per field type","eventName":"__pest_evaluable_validation__rules_are_sanitized_against_a_whitelist_per_field_type","line":72},{"name":"a field key cannot change once content values exist for it","eventName":"__pest_evaluable_a_field_key_cannot_change_once_content_values_exist_for_it","line":89},{"name":"a schema key cannot change and the schema cannot be deleted once it has stored content","eventName":"__pest_evaluable_a_schema_key_cannot_change_and_the_schema_cannot_be_deleted_once_it_has_stored_content","line":114},{"name":"reordering fields persists the requested order and ignores foreign ids","eventName":"__pest_evaluable_reordering_fields_persists_the_requested_order_and_ignores_foreign_ids","line":140},{"name":"workspace A cannot load workspace B field schemas through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_load_workspace_B_field_schemas_through_the_global_scope","line":156}],"name":"FieldSchemaTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/FormsAdminTest.php","tests":[{"name":"the forms list shows each form with its answer counts","eventName":"__pest_evaluable_the_forms_list_shows_each_form_with_its_answer_counts","line":45},{"name":"creating a form from the list opens the builder","eventName":"__pest_evaluable_creating_a_form_from_the_list_opens_the_builder","line":55},{"name":"the builder loads a form, saves changes and refuses an invalid definition","eventName":"__pest_evaluable_the_builder_loads_a_form__saves_changes_and_refuses_an_invalid_definition","line":68},{"name":"the builder can add, move and remove select options","eventName":"__pest_evaluable_the_builder_can_add__move_and_remove_select_options","line":88},{"name":"answers are listed under their form and can be filtered and searched","eventName":"__pest_evaluable_answers_are_listed_under_their_form_and_can_be_filtered_and_searched","line":101},{"name":"an answer can be opened, given a status and a note, and bulk-updated","eventName":"__pest_evaluable_an_answer_can_be_opened__given_a_status_and_a_note__and_bulk_updated","line":117},{"name":"opening an answer does not change its status by itself","eventName":"__pest_evaluable_opening_an_answer_does_not_change_its_status_by_itself","line":135},{"name":"answers of another form of the same website cannot be opened through this one","eventName":"__pest_evaluable_answers_of_another_form_of_the_same_website_cannot_be_opened_through_this_one","line":144},{"name":"the CSV export contains the answers, respects the status filter and defuses formulas","eventName":"__pest_evaluable_the_CSV_export_contains_the_answers__respects_the_status_filter_and_defuses_formulas","line":156},{"name":"a member without the forms permissions cannot reach any of the pages","eventName":"__pest_evaluable_a_member_without_the_forms_permissions_cannot_reach_any_of_the_pages","line":174},{"name":"a form can only be reached through its own website","eventName":"__pest_evaluable_a_form_can_only_be_reached_through_its_own_website","line":186},{"name":"the page editor offers the website forms as a picker and saves the chosen one","eventName":"__pest_evaluable_the_page_editor_offers_the_website_forms_as_a_picker_and_saves_the_chosen_one","line":194},{"name":"a section that cannot be saved says why inside the editor instead of failing silently","eventName":"__pest_evaluable_a_section_that_cannot_be_saved_says_why_inside_the_editor_instead_of_failing_silently","line":217}],"name":"FormsAdminTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/FormsApiTest.php","tests":[{"name":"a submitted form is stored with its answers, the labels it had, the language and where it came from","eventName":"__pest_evaluable_a_submitted_form_is_stored_with_its_answers__the_labels_it_had__the_language_and_where_it_came_from","line":23},{"name":"the success message comes back in the language the visitor used","eventName":"__pest_evaluable_the_success_message_comes_back_in_the_language_the_visitor_used","line":48},{"name":"a wrong answer is refused with a message per field and nothing is stored","eventName":"__pest_evaluable_a_wrong_answer_is_refused_with_a_message_per_field_and_nothing_is_stored","line":58},{"name":"the answer to a validation failure is JSON even when the caller sends no Accept header","eventName":"__pest_evaluable_the_answer_to_a_validation_failure_is_JSON_even_when_the_caller_sends_no_Accept_header","line":71},{"name":"fields the form does not define are ignored","eventName":"__pest_evaluable_fields_the_form_does_not_define_are_ignored","line":79},{"name":"a bot that fills the hidden field is told it worked but nothing is stored","eventName":"__pest_evaluable_a_bot_that_fills_the_hidden_field_is_told_it_worked_but_nothing_is_stored","line":88},{"name":"an inactive, unknown or another site's form cannot be submitted to","eventName":"__pest_evaluable_an_inactive__unknown_or_another_site_s_form_cannot_be_submitted_to","line":97},{"name":"the definition is served in the requested language and falls back to the default one","eventName":"__pest_evaluable_the_definition_is_served_in_the_requested_language_and_falls_back_to_the_default_one","line":114},{"name":"a section that references a form embeds it, and the form is edited once for every section that uses it","eventName":"__pest_evaluable_a_section_that_references_a_form_embeds_it__and_the_form_is_edited_once_for_every_section_that_uses_it","line":129},{"name":"a section cannot reference a form of another website","eventName":"__pest_evaluable_a_section_cannot_reference_a_form_of_another_website","line":169},{"name":"a form with answers cannot be deleted or lose a field, but its wording can change","eventName":"__pest_evaluable_a_form_with_answers_cannot_be_deleted_or_lose_a_field__but_its_wording_can_change","line":187},{"name":"the builder refuses a broken definition with a clear message","eventName":"__pest_evaluable_the_builder_refuses_a_broken_definition_with_a_clear_message","line":211},{"name":"definition rules are checked in one place","eventName":"__pest_evaluable_definition_rules_are_checked_in_one_place","line":225}],"name":"FormsApiTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/GlobalContentTest.php","tests":[{"name":"a global content group has exactly one instance and its value can be saved and loaded","eventName":"__pest_evaluable_a_global_content_group_has_exactly_one_instance_and_its_value_can_be_saved_and_loaded","line":9},{"name":"the global content index and values pages render without error","eventName":"__pest_evaluable_the_global_content_index_and_values_pages_render_without_error","line":34},{"name":"a section type and a global content group can share the same field schema engine without colliding","eventName":"__pest_evaluable_a_section_type_and_a_global_content_group_can_share_the_same_field_schema_engine_without_colliding","line":47}],"name":"GlobalContentTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/MediaTest.php","tests":[{"name":"the owner can upload and delete media, stored on the public disk","eventName":"__pest_evaluable_the_owner_can_upload_and_delete_media__stored_on_the_public_disk","line":8},{"name":"a write that silently fails on disk throws instead of leaving a media row with no real file","eventName":"__pest_evaluable_a_write_that_silently_fails_on_disk_throws_instead_of_leaving_a_media_row_with_no_real_file","line":27},{"name":"workspace A cannot load workspace B media through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_load_workspace_B_media_through_the_global_scope","line":48}],"name":"MediaTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/MenuTest.php","tests":[{"name":"a menu can be created with nested items of different types","eventName":"__pest_evaluable_a_menu_can_be_created_with_nested_items_of_different_types","line":10},{"name":"a menu item referencing a page from a different website is rejected","eventName":"__pest_evaluable_a_menu_item_referencing_a_page_from_a_different_website_is_rejected","line":32},{"name":"an external_url item requires a url","eventName":"__pest_evaluable_an_external__url_item_requires_a_url","line":45},{"name":"menu items can be reordered within the same parent","eventName":"__pest_evaluable_menu_items_can_be_reordered_within_the_same_parent","line":56},{"name":"deleting a parent item cascades to its children","eventName":"__pest_evaluable_deleting_a_parent_item_cascades_to_its_children","line":71},{"name":"the menu edit screen renders with nested items","eventName":"__pest_evaluable_the_menu_edit_screen_renders_with_nested_items","line":85},{"name":"workspace A cannot load workspace B menus through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_load_workspace_B_menus_through_the_global_scope","line":101}],"name":"MenuTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/PageSectionTest.php","tests":[{"name":"a section can be added to a page, its content saved, duplicated, and removed","eventName":"__pest_evaluable_a_section_can_be_added_to_a_page__its_content_saved__duplicated__and_removed","line":15},{"name":"a required translatable field cannot be saved empty","eventName":"__pest_evaluable_a_required_translatable_field_cannot_be_saved_empty","line":45},{"name":"sections can be reordered","eventName":"__pest_evaluable_sections_can_be_reordered","line":61},{"name":"a section type from a different website cannot be added to a page","eventName":"__pest_evaluable_a_section_type_from_a_different_website_cannot_be_added_to_a_page","line":78}],"name":"PageSectionTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/PageTest.php","tests":[{"name":"the owner can create and update a page with translations","eventName":"__pest_evaluable_the_owner_can_create_and_update_a_page_with_translations","line":12},{"name":"a page key must be unique within the website","eventName":"__pest_evaluable_a_page_key_must_be_unique_within_the_website","line":34},{"name":"a slug must be unique within the website and language","eventName":"__pest_evaluable_a_slug_must_be_unique_within_the_website_and_language","line":45},{"name":"a page cannot be published without at least one translation","eventName":"__pest_evaluable_a_page_cannot_be_published_without_at_least_one_translation","line":61},{"name":"publishing a page sets published_at and the status transitions correctly","eventName":"__pest_evaluable_publishing_a_page_sets_published__at_and_the_status_transitions_correctly","line":72},{"name":"deleting a page is a soft delete","eventName":"__pest_evaluable_deleting_a_page_is_a_soft_delete","line":88},{"name":"workspace A cannot load workspace B pages through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_load_workspace_B_pages_through_the_global_scope","line":101},{"name":"the page edit screen renders with sections, seo, and general info","eventName":"__pest_evaluable_the_page_edit_screen_renders_with_sections__seo__and_general_info","line":116},{"name":"the pages index page lists and creates via the Livewire component","eventName":"__pest_evaluable_the_pages_index_page_lists_and_creates_via_the_Livewire_component","line":133}],"name":"PageTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/PublicApiTest.php","tests":[{"name":"the public config and languages endpoints return data for an active website without authentication","eventName":"__pest_evaluable_the_public_config_and_languages_endpoints_return_data_for_an_active_website_without_authentication","line":21},{"name":"an unknown website key returns 404","eventName":"__pest_evaluable_an_unknown_website_key_returns_404","line":36},{"name":"a draft page is never returned by the public api but a published one is","eventName":"__pest_evaluable_a_draft_page_is_never_returned_by_the_public_api_but_a_published_one_is","line":40},{"name":"an inactive section is excluded from the resolved page but an active one with content is included","eventName":"__pest_evaluable_an_inactive_section_is_excluded_from_the_resolved_page_but_an_active_one_with_content_is_included","line":60},{"name":"page translations are returned for hreflang and an unsupported locale 404s","eventName":"__pest_evaluable_page_translations_are_returned_for_hreflang_and_an_unsupported_locale_404s","line":88},{"name":"only published posts are listed and shown, and category filtering works","eventName":"__pest_evaluable_only_published_posts_are_listed_and_shown__and_category_filtering_works","line":111},{"name":"a menu resolves to a nested tree with a plain url per item","eventName":"__pest_evaluable_a_menu_resolves_to_a_nested_tree_with_a_plain_url_per_item","line":137},{"name":"redirects endpoint lists only active redirects","eventName":"__pest_evaluable_redirects_endpoint_lists_only_active_redirects","line":158},{"name":"a page from workspace B cannot be fetched through workspace A's website key","eventName":"__pest_evaluable_a_page_from_workspace_B_cannot_be_fetched_through_workspace_A_s_website_key","line":171},{"name":"the preview endpoint requires a valid signature and returns draft content when signed","eventName":"__pest_evaluable_the_preview_endpoint_requires_a_valid_signature_and_returns_draft_content_when_signed","line":190},{"name":"global content is returned keyed by group key","eventName":"__pest_evaluable_global_content_is_returned_keyed_by_group_key","line":217}],"name":"PublicApiTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/RedirectTest.php","tests":[{"name":"a redirect can be created, updated, and deleted","eventName":"__pest_evaluable_a_redirect_can_be_created__updated__and_deleted","line":8},{"name":"a redirect cannot point to itself","eventName":"__pest_evaluable_a_redirect_cannot_point_to_itself","line":24},{"name":"a redirect that would create a direct A-B B-A loop is rejected","eventName":"__pest_evaluable_a_redirect_that_would_create_a_direct_A_B_B_A_loop_is_rejected","line":33},{"name":"a source path must be unique within the website","eventName":"__pest_evaluable_a_source_path_must_be_unique_within_the_website","line":44},{"name":"the redirects index page renders","eventName":"__pest_evaluable_the_redirects_index_page_renders","line":55},{"name":"workspace A cannot load workspace B redirects through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_load_workspace_B_redirects_through_the_global_scope","line":66}],"name":"RedirectTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/SeoTest.php","tests":[{"name":"seo meta can be set and resolved for a page in a given language","eventName":"__pest_evaluable_seo_meta_can_be_set_and_resolved_for_a_page_in_a_given_language","line":12},{"name":"seo resolves to the website_defaults global content group when resource-specific seo is missing","eventName":"__pest_evaluable_seo_resolves_to_the_website__defaults_global_content_group_when_resource_specific_seo_is_missing","line":28},{"name":"seo completeness warnings list missing title, description, og image, and slug","eventName":"__pest_evaluable_seo_completeness_warnings_list_missing_title__description__og_image__and_slug","line":46},{"name":"workspace A cannot load workspace B seo meta through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_load_workspace_B_seo_meta_through_the_global_scope","line":58}],"name":"SeoTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/WebsiteLanguageTest.php","tests":[{"name":"the first language added to a website automatically becomes the default active language","eventName":"__pest_evaluable_the_first_language_added_to_a_website_automatically_becomes_the_default_active_language","line":9},{"name":"adding a second language does not change the default unless requested","eventName":"__pest_evaluable_adding_a_second_language_does_not_change_the_default_unless_requested","line":22},{"name":"setting a new default language unsets the previous one","eventName":"__pest_evaluable_setting_a_new_default_language_unsets_the_previous_one","line":34},{"name":"the default language cannot be disabled","eventName":"__pest_evaluable_the_default_language_cannot_be_disabled","line":49},{"name":"the default language cannot be deleted","eventName":"__pest_evaluable_the_default_language_cannot_be_deleted","line":60},{"name":"a non-default language can be deleted","eventName":"__pest_evaluable_a_non_default_language_can_be_deleted","line":70},{"name":"locale must be unique within a website","eventName":"__pest_evaluable_locale_must_be_unique_within_a_website","line":83},{"name":"a language cannot be its own fallback","eventName":"__pest_evaluable_a_language_cannot_be_its_own_fallback","line":94}],"name":"WebsiteLanguageTest","directories":["Cms"]},{"path":"tests\/Feature\/Cms\/WebsiteTest.php","tests":[{"name":"the owner can create, update, and soft-delete a website","eventName":"__pest_evaluable_the_owner_can_create__update__and_soft_delete_a_website","line":12},{"name":"a website key must be unique within the workspace","eventName":"__pest_evaluable_a_website_key_must_be_unique_within_the_workspace","line":28},{"name":"a website key must be globally unique across every workspace, since the public API addresses a website by key alone","eventName":"__pest_evaluable_a_website_key_must_be_globally_unique_across_every_workspace__since_the_public_API_addresses_a_website_by_key_alone","line":38},{"name":"a seller cannot manage websites","eventName":"__pest_evaluable_a_seller_cannot_manage_websites","line":50},{"name":"workspace A cannot see or load workspace B websites through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_see_or_load_workspace_B_websites_through_the_global_scope","line":59},{"name":"the websites index page lists, creates, and deletes via the Livewire component","eventName":"__pest_evaluable_the_websites_index_page_lists__creates__and_deletes_via_the_Livewire_component","line":73}],"name":"WebsiteTest","directories":["Cms"]},{"path":"tests\/Feature\/Commissions\/CommissionEngineTest.php","tests":[{"name":"resolveRule prefers a catalog_item-scoped rule over a project-scoped rule","eventName":"__pest_evaluable_resolveRule_prefers_a_catalog__item_scoped_rule_over_a_project_scoped_rule","line":60},{"name":"resolveRule prefers a scope+user rule over a scope-only rule at the same level","eventName":"__pest_evaluable_resolveRule_prefers_a_scope_user_rule_over_a_scope_only_rule_at_the_same_level","line":71},{"name":"resolveRule ignores inactive and expired rules","eventName":"__pest_evaluable_resolveRule_ignores_inactive_and_expired_rules","line":82},{"name":"calculateDealCommissions creates a payable entry using the resolved rule","eventName":"__pest_evaluable_calculateDealCommissions_creates_a_payable_entry_using_the_resolved_rule","line":94},{"name":"calculateDealCommissions is idempotent for a deal that already has entries","eventName":"__pest_evaluable_calculateDealCommissions_is_idempotent_for_a_deal_that_already_has_entries","line":110},{"name":"calculateDealCommissions splits commission across collaborators by share_percent","eventName":"__pest_evaluable_calculateDealCommissions_splits_commission_across_collaborators_by_share__percent","line":122},{"name":"falls back to the item own commission snapshot when no rule resolves","eventName":"__pest_evaluable_falls_back_to_the_item_own_commission_snapshot_when_no_rule_resolves","line":144},{"name":"no rule and no item snapshot produces no commission entries","eventName":"__pest_evaluable_no_rule_and_no_item_snapshot_produces_no_commission_entries","line":159},{"name":"updateEligibility recomputes eligible_amount proportional to collected payments for apply_after_collection rules","eventName":"__pest_evaluable_updateEligibility_recomputes_eligible__amount_proportional_to_collected_payments_for_apply__after__collection_rules","line":167},{"name":"a rule not gated on collection is fully eligible immediately","eventName":"__pest_evaluable_a_rule_not_gated_on_collection_is_fully_eligible_immediately","line":188}],"name":"CommissionEngineTest","directories":["Commissions"]},{"path":"tests\/Feature\/Commissions\/CommissionEntryStateMachineTest.php","tests":[{"name":"review moves an estimated entry to pending_review","eventName":"__pest_evaluable_review_moves_an_estimated_entry_to_pending__review","line":50},{"name":"approve auto-promotes to payable when the entry is not gated on collection","eventName":"__pest_evaluable_approve_auto_promotes_to_payable_when_the_entry_is_not_gated_on_collection","line":58},{"name":"approve leaves an apply_after_collection entry at approved until fully eligible","eventName":"__pest_evaluable_approve_leaves_an_apply__after__collection_entry_at_approved_until_fully_eligible","line":66},{"name":"dispute requires a reason and moves the entry to disputed","eventName":"__pest_evaluable_dispute_requires_a_reason_and_moves_the_entry_to_disputed","line":79},{"name":"cancel is allowed from a non-terminal status with a reason","eventName":"__pest_evaluable_cancel_is_allowed_from_a_non_terminal_status_with_a_reason","line":89},{"name":"a paid entry cannot be canceled (terminal state)","eventName":"__pest_evaluable_a_paid_entry_cannot_be_canceled__terminal_state_","line":97},{"name":"a seller without commissions.approve cannot approve an entry","eventName":"__pest_evaluable_a_seller_without_commissions_approve_cannot_approve_an_entry","line":104},{"name":"the beneficiary can view their own entry via commissions.view_own without commissions.view_all","eventName":"__pest_evaluable_the_beneficiary_can_view_their_own_entry_via_commissions_view__own_without_commissions_view__all","line":117},{"name":"workspace A cannot see workspace B commission entries","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_commission_entries","line":129}],"name":"CommissionEntryStateMachineTest","directories":["Commissions"]},{"path":"tests\/Feature\/Contracts\/ContractFormValidationTest.php","tests":[{"name":"creating a contract with blank optional date fields succeeds and stores null","eventName":"__pest_evaluable_creating_a_contract_with_blank_optional_date_fields_succeeds_and_stores_null","line":16},{"name":"a new contract defaults to draft but its status can be chosen at creation","eventName":"__pest_evaluable_a_new_contract_defaults_to_draft_but_its_status_can_be_chosen_at_creation","line":37},{"name":"the status of an existing contract can be changed from the edit form","eventName":"__pest_evaluable_the_status_of_an_existing_contract_can_be_changed_from_the_edit_form","line":61},{"name":"an unknown contract status is rejected","eventName":"__pest_evaluable_an_unknown_contract_status_is_rejected","line":78}],"name":"ContractFormValidationTest","directories":["Contracts"]},{"path":"tests\/Feature\/Contracts\/ContractsShowTest.php","tests":[{"name":"the support tab lists periods and a sales manager can create one","eventName":"__pest_evaluable_the_support_tab_lists_periods_and_a_sales_manager_can_create_one","line":10},{"name":"a seller can open the support period form (has contract update permission)","eventName":"__pest_evaluable_a_seller_can_open_the_support_period_form__has_contract_update_permission_","line":28},{"name":"a sales manager cannot open the support period form (can view but not update contracts)","eventName":"__pest_evaluable_a_sales_manager_cannot_open_the_support_period_form__can_view_but_not_update_contracts_","line":41},{"name":"editing a period preloads its schedule into the form","eventName":"__pest_evaluable_editing_a_period_preloads_its_schedule_into_the_form","line":54}],"name":"ContractsShowTest","directories":["Contracts"]},{"path":"tests\/Feature\/Contracts\/SupportPeriodTest.php","tests":[{"name":"a sales manager can create a support period with a weekly schedule","eventName":"__pest_evaluable_a_sales_manager_can_create_a_support_period_with_a_weekly_schedule","line":19},{"name":"a seller cannot create a support period","eventName":"__pest_evaluable_a_seller_cannot_create_a_support_period","line":43},{"name":"updating a support period replaces its schedule rows","eventName":"__pest_evaluable_updating_a_support_period_replaces_its_schedule_rows","line":62},{"name":"deleting a support period cascades its schedules, holidays, and sla rules","eventName":"__pest_evaluable_deleting_a_support_period_cascades_its_schedules__holidays__and_sla_rules","line":91},{"name":"holidays and SLA rules can be added and removed","eventName":"__pest_evaluable_holidays_and_SLA_rules_can_be_added_and_removed","line":119}],"name":"SupportPeriodTest","directories":["Contracts"]},{"path":"tests\/Feature\/Customers\/CustomerActionsTest.php","tests":[{"name":"creating a customer defaults ownership to the acting user and creates an owner assignment","eventName":"__pest_evaluable_creating_a_customer_defaults_ownership_to_the_acting_user_and_creates_an_owner_assignment","line":15},{"name":"a seller can update their own customer but not someone else's","eventName":"__pest_evaluable_a_seller_can_update_their_own_customer_but_not_someone_else_s","line":25},{"name":"transferring ownership updates owner_user_id, flips the old assignment, and is audited","eventName":"__pest_evaluable_transferring_ownership_updates_owner__user__id__flips_the_old_assignment__and_is_audited","line":43},{"name":"a seller cannot transfer ownership without the permission","eventName":"__pest_evaluable_a_seller_cannot_transfer_ownership_without_the_permission","line":62},{"name":"assigning a customer creates an active assignment","eventName":"__pest_evaluable_assigning_a_customer_creates_an_active_assignment","line":76},{"name":"merging duplicates re-points contacts, assignments, and activities then soft-deletes the duplicate","eventName":"__pest_evaluable_merging_duplicates_re_points_contacts__assignments__and_activities_then_soft_deletes_the_duplicate","line":93},{"name":"scheduling a followup updates next_followup_at and creates a task activity","eventName":"__pest_evaluable_scheduling_a_followup_updates_next__followup__at_and_creates_a_task_activity","line":117},{"name":"workspace A cannot see workspace B customers","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_customers","line":131}],"name":"CustomerActionsTest","directories":["Customers"]},{"path":"tests\/Feature\/Customers\/CustomerContactActionsTest.php","tests":[{"name":"creating a contact attaches it to the customer and audits the event","eventName":"__pest_evaluable_creating_a_contact_attaches_it_to_the_customer_and_audits_the_event","line":14},{"name":"a seller can add a contact to their own customer but not to someone else's","eventName":"__pest_evaluable_a_seller_can_add_a_contact_to_their_own_customer_but_not_to_someone_else_s","line":30},{"name":"updating a contact changes its fields and is audited","eventName":"__pest_evaluable_updating_a_contact_changes_its_fields_and_is_audited","line":48},{"name":"deleting a contact removes it and is audited","eventName":"__pest_evaluable_deleting_a_contact_removes_it_and_is_audited","line":61},{"name":"opening, saving, editing, and deleting a contact works through the full Livewire lifecycle","eventName":"__pest_evaluable_opening__saving__editing__and_deleting_a_contact_works_through_the_full_Livewire_lifecycle","line":84}],"name":"CustomerContactActionsTest","directories":["Customers"]},{"path":"tests\/Feature\/Customers\/CustomerShowRouteTest.php","tests":[{"name":"visiting a customer show page directly does not throw NoWorkspaceContextException","eventName":"__pest_evaluable_visiting_a_customer_show_page_directly_does_not_throw_NoWorkspaceContextException","line":17}],"name":"CustomerShowRouteTest","directories":["Customers"]},{"path":"tests\/Feature\/Customers\/CustomerTimelineTest.php","tests":[{"name":"logging a call as already-completed sets status, completed_at, and result immediately","eventName":"__pest_evaluable_logging_a_call_as_already_completed_sets_status__completed__at__and_result_immediately","line":7},{"name":"scheduling a future call sets due_at and leaves it pending for the reminder to pick up","eventName":"__pest_evaluable_scheduling_a_future_call_sets_due__at_and_leaves_it_pending_for_the_reminder_to_pick_up","line":31},{"name":"checking notify_team on the create form persists it on the activity","eventName":"__pest_evaluable_checking_notify__team_on_the_create_form_persists_it_on_the_activity","line":53},{"name":"completing a pending activity from the timeline works through the full Livewire lifecycle","eventName":"__pest_evaluable_completing_a_pending_activity_from_the_timeline_works_through_the_full_Livewire_lifecycle","line":73}],"name":"CustomerTimelineTest","directories":["Customers"]},{"path":"tests\/Feature\/Customers\/CustomerVisibilityTest.php","tests":[{"name":"rule 1: a user with customers.view_all sees every customer","eventName":"__pest_evaluable_rule_1__a_user_with_customers_view__all_sees_every_customer","line":24},{"name":"rule 3: a seller sees a customer they own","eventName":"__pest_evaluable_rule_3__a_seller_sees_a_customer_they_own","line":36},{"name":"rule 4: a seller sees a customer they have an active assignment on, even if not the owner","eventName":"__pest_evaluable_rule_4__a_seller_sees_a_customer_they_have_an_active_assignment_on__even_if_not_the_owner","line":48},{"name":"rule 2: a sales manager with team permission sees a direct report customer","eventName":"__pest_evaluable_rule_2__a_sales_manager_with_team_permission_sees_a_direct_report_customer","line":71},{"name":"rule 5: team-visible customers are visible to anyone holding the team permission","eventName":"__pest_evaluable_rule_5__team_visible_customers_are_visible_to_anyone_holding_the_team_permission","line":85},{"name":"rule 6: project-visible customers are visible to a manager of the shared project","eventName":"__pest_evaluable_rule_6__project_visible_customers_are_visible_to_a_manager_of_the_shared_project","line":102},{"name":"rule 7: an unrelated seller cannot see a private customer owned by someone else","eventName":"__pest_evaluable_rule_7__an_unrelated_seller_cannot_see_a_private_customer_owned_by_someone_else","line":130},{"name":"the visible query scope matches canView for a mixed set of customers","eventName":"__pest_evaluable_the_visible_query_scope_matches_canView_for_a_mixed_set_of_customers","line":147}],"name":"CustomerVisibilityTest","directories":["Customers"]},{"path":"tests\/Feature\/Dashboard\/DashboardAnalyticsRenderTest.php","tests":[{"name":"the page renders inside the RTL application layout","eventName":"__pest_evaluable_the_page_renders_inside_the_RTL_application_layout","line":40},{"name":"numeric values are marked for latin numeral rendering","eventName":"__pest_evaluable_numeric_values_are_marked_for_latin_numeral_rendering","line":47},{"name":"every widget reuses the existing panel design tokens rather than new colours","eventName":"__pest_evaluable_every_widget_reuses_the_existing_panel_design_tokens_rather_than_new_colours","line":54},{"name":"the layout collapses to a single column on small screens","eventName":"__pest_evaluable_the_layout_collapses_to_a_single_column_on_small_screens","line":62},{"name":"drill-down links point at the existing list screens with a filter","eventName":"__pest_evaluable_drill_down_links_point_at_the_existing_list_screens_with_a_filter","line":74},{"name":"a widget with no data says so instead of drawing an empty chart","eventName":"__pest_evaluable_a_widget_with_no_data_says_so_instead_of_drawing_an_empty_chart","line":82},{"name":"the seller view omits the team table entirely from the markup","eventName":"__pest_evaluable_the_seller_view_omits_the_team_table_entirely_from_the_markup","line":93},{"name":"the dashboard is reachable from the main navigation","eventName":"__pest_evaluable_the_dashboard_is_reachable_from_the_main_navigation","line":107}],"name":"DashboardAnalyticsRenderTest","directories":["Dashboard"]},{"path":"tests\/Feature\/Dashboard\/DashboardAnalyticsTest.php","tests":[{"name":"kpi figures match what is actually stored","eventName":"__pest_evaluable_kpi_figures_match_what_is_actually_stored","line":35},{"name":"the funnel is built from the workspace stages, in their configured order","eventName":"__pest_evaluable_the_funnel_is_built_from_the_workspace_stages__in_their_configured_order","line":59},{"name":"the weighted forecast uses each stage real probability","eventName":"__pest_evaluable_the_weighted_forecast_uses_each_stage_real_probability","line":85},{"name":"changing a filter changes the figures without leaving the page","eventName":"__pest_evaluable_changing_a_filter_changes_the_figures_without_leaving_the_page","line":98},{"name":"a seller cannot widen the dashboard beyond their own figures","eventName":"__pest_evaluable_a_seller_cannot_widen_the_dashboard_beyond_their_own_figures","line":120},{"name":"a seller does not receive the team comparison at all","eventName":"__pest_evaluable_a_seller_does_not_receive_the_team_comparison_at_all","line":146},{"name":"the period filter narrows the window and resets a stale custom range","eventName":"__pest_evaluable_the_period_filter_narrows_the_window_and_resets_a_stale_custom_range","line":159},{"name":"lead sources are judged by conversion and revenue, not volume alone","eventName":"__pest_evaluable_lead_sources_are_judged_by_conversion_and_revenue__not_volume_alone","line":181},{"name":"an empty range yields no figures rather than invented ones","eventName":"__pest_evaluable_an_empty_range_yields_no_figures_rather_than_invented_ones","line":202},{"name":"aging buckets every open deal exactly once","eventName":"__pest_evaluable_aging_buckets_every_open_deal_exactly_once","line":216},{"name":"the follow-up counts ignore the date filter, because overdue stays overdue","eventName":"__pest_evaluable_the_follow_up_counts_ignore_the_date_filter__because_overdue_stays_overdue","line":232},{"name":"follow-up counts stay scoped to the seller looking at them","eventName":"__pest_evaluable_follow_up_counts_stay_scoped_to_the_seller_looking_at_them","line":257},{"name":"needs-attention only lists things the data can prove are waiting","eventName":"__pest_evaluable_needs_attention_only_lists_things_the_data_can_prove_are_waiting","line":271},{"name":"needs-attention is empty when nothing is actually overdue","eventName":"__pest_evaluable_needs_attention_is_empty_when_nothing_is_actually_overdue","line":301},{"name":"a brand-new lead is not flagged until it has been left for a day","eventName":"__pest_evaluable_a_brand_new_lead_is_not_flagged_until_it_has_been_left_for_a_day","line":315},{"name":"the dashboard page loads for a permitted member","eventName":"__pest_evaluable_the_dashboard_page_loads_for_a_permitted_member","line":332}],"name":"DashboardAnalyticsTest","directories":["Dashboard"]},{"path":"tests\/Feature\/Dashboard\/DashboardIndexDesignTest.php","tests":[{"name":"no dashboard view uses hard-coded colours instead of the panel tokens","eventName":"__pest_evaluable_no_dashboard_view_uses_hard_coded_colours_instead_of_the_panel_tokens","line":26},{"name":"the personal view links its figures to the screens behind them","eventName":"__pest_evaluable_the_personal_view_links_its_figures_to_the_screens_behind_them","line":41},{"name":"each view offers the three tabs and a route to the deeper analysis","eventName":"__pest_evaluable_each_view_offers_the_three_tabs_and_a_route_to_the_deeper_analysis","line":54},{"name":"a seller sees neither the tabs nor the analytics link","eventName":"__pest_evaluable_a_seller_sees_neither_the_tabs_nor_the_analytics_link","line":62},{"name":"empty panels explain themselves instead of showing a blank box","eventName":"__pest_evaluable_empty_panels_explain_themselves_instead_of_showing_a_blank_box","line":77},{"name":"the sales trend is labelled with Jalali months for a Persian reader","eventName":"__pest_evaluable_the_sales_trend_is_labelled_with_Jalali_months_for_a_Persian_reader","line":84},{"name":"the financial view derives its collection ratio from figures already shown","eventName":"__pest_evaluable_the_financial_view_derives_its_collection_ratio_from_figures_already_shown","line":101},{"name":"the financial view says so when there is nothing to compute a ratio from","eventName":"__pest_evaluable_the_financial_view_says_so_when_there_is_nothing_to_compute_a_ratio_from","line":114},{"name":"the permissions guide follows the same design tokens","eventName":"__pest_evaluable_the_permissions_guide_follows_the_same_design_tokens","line":120}],"name":"DashboardIndexDesignTest","directories":["Dashboard"]},{"path":"tests\/Feature\/Dashboard\/DashboardJalaliTest.php","tests":[{"name":"the month period starts on the first of the Jalali month, not the Gregorian one","eventName":"__pest_evaluable_the_month_period_starts_on_the_first_of_the_Jalali_month__not_the_Gregorian_one","line":8},{"name":"the year period starts on the first of Farvardin","eventName":"__pest_evaluable_the_year_period_starts_on_the_first_of_Farvardin","line":20},{"name":"the Persian week starts on Saturday","eventName":"__pest_evaluable_the_Persian_week_starts_on_Saturday","line":30},{"name":"an English locale keeps Gregorian boundaries","eventName":"__pest_evaluable_an_English_locale_keeps_Gregorian_boundaries","line":38},{"name":"a range never extends past now, so a to-date figure is not diluted","eventName":"__pest_evaluable_a_range_never_extends_past_now__so_a_to_date_figure_is_not_diluted","line":49},{"name":"audit events are shown in Persian","eventName":"__pest_evaluable_audit_events_are_shown_in_Persian","line":58},{"name":"every audit event the code emits has a Persian label","eventName":"__pest_evaluable_every_audit_event_the_code_emits_has_a_Persian_label","line":71},{"name":"an unknown event falls back to its raw key instead of a wrong label","eventName":"__pest_evaluable_an_unknown_event_falls_back_to_its_raw_key_instead_of_a_wrong_label","line":92},{"name":"the dashboard renders the range in Jalali and events in Persian","eventName":"__pest_evaluable_the_dashboard_renders_the_range_in_Jalali_and_events_in_Persian","line":98}],"name":"DashboardJalaliTest","directories":["Dashboard"]},{"path":"tests\/Feature\/Dashboard\/DashboardMetricsTest.php","tests":[{"name":"forSeller aggregates only the given user own activities, deals and commissions","eventName":"__pest_evaluable_forSeller_aggregates_only_the_given_user_own_activities__deals_and_commissions","line":10},{"name":"forManager aggregates across the whole workspace, not one seller","eventName":"__pest_evaluable_forManager_aggregates_across_the_whole_workspace__not_one_seller","line":34},{"name":"a seller without reports.view is redirected away from the team dashboard","eventName":"__pest_evaluable_a_seller_without_reports_view_is_redirected_away_from_the_team_dashboard","line":55}],"name":"DashboardMetricsTest","directories":["Dashboard"]},{"path":"tests\/Feature\/Dashboard\/DashboardOverdueActivitiesQuickActionTest.php","tests":[{"name":"the personal dashboard overdue panel offers a quick edit link and complete button per activity","eventName":"__pest_evaluable_the_personal_dashboard_overdue_panel_offers_a_quick_edit_link_and_complete_button_per_activity","line":8},{"name":"completing an activity from the dashboard removes it from the overdue panel without a full reload","eventName":"__pest_evaluable_completing_an_activity_from_the_dashboard_removes_it_from_the_overdue_panel_without_a_full_reload","line":25},{"name":"a role without ActivitiesUpdate cannot quick-complete from the dashboard","eventName":"__pest_evaluable_a_role_without_ActivitiesUpdate_cannot_quick_complete_from_the_dashboard","line":40}],"name":"DashboardOverdueActivitiesQuickActionTest","directories":["Dashboard"]},{"path":"tests\/Feature\/Dashboard\/DealLostReasonTest.php","tests":[{"name":"marking a deal lost stores the reason on the deal, not only in the audit log","eventName":"__pest_evaluable_marking_a_deal_lost_stores_the_reason_on_the_deal__not_only_in_the_audit_log","line":30},{"name":"a reason is optional and its absence is recorded as such","eventName":"__pest_evaluable_a_reason_is_optional_and_its_absence_is_recorded_as_such","line":42},{"name":"the deal screen captures the reason chosen in the dialog","eventName":"__pest_evaluable_the_deal_screen_captures_the_reason_chosen_in_the_dialog","line":52},{"name":"an unrecognised reason is discarded rather than stored","eventName":"__pest_evaluable_an_unrecognised_reason_is_discarded_rather_than_stored","line":65},{"name":"the report groups lost deals by reason with counts and value","eventName":"__pest_evaluable_the_report_groups_lost_deals_by_reason_with_counts_and_value","line":78},{"name":"deals lost without a reason are reported separately, never folded into a bucket","eventName":"__pest_evaluable_deals_lost_without_a_reason_are_reported_separately__never_folded_into_a_bucket","line":101},{"name":"the report is empty when nothing has been lost","eventName":"__pest_evaluable_the_report_is_empty_when_nothing_has_been_lost","line":123},{"name":"the dashboard shows the breakdown, and says so when no reason was recorded","eventName":"__pest_evaluable_the_dashboard_shows_the_breakdown__and_says_so_when_no_reason_was_recorded","line":135}],"name":"DealLostReasonTest","directories":["Dashboard"]},{"path":"tests\/Feature\/Dashboard\/SalesTargetTest.php","tests":[{"name":"no target means the dashboard reports actuals only","eventName":"__pest_evaluable_no_target_means_the_dashboard_reports_actuals_only","line":29},{"name":"a team target is compared against the whole workspace actuals","eventName":"__pest_evaluable_a_team_target_is_compared_against_the_whole_workspace_actuals","line":43},{"name":"filtering by seller compares against that seller own target","eventName":"__pest_evaluable_filtering_by_seller_compares_against_that_seller_own_target","line":66},{"name":"a target outside the selected window is not counted","eventName":"__pest_evaluable_a_target_outside_the_selected_window_is_not_counted","line":90},{"name":"the settings screen saves a target and revises it in place","eventName":"__pest_evaluable_the_settings_screen_saves_a_target_and_revises_it_in_place","line":108},{"name":"the target form rejects a missing amount and a non-member","eventName":"__pest_evaluable_the_target_form_rejects_a_missing_amount_and_a_non_member","line":131},{"name":"a member without settings permission cannot open or change targets","eventName":"__pest_evaluable_a_member_without_settings_permission_cannot_open_or_change_targets","line":148},{"name":"the dashboard shows target progress once a target exists","eventName":"__pest_evaluable_the_dashboard_shows_target_progress_once_a_target_exists","line":158},{"name":"the dashboard invites setting a target when none exists","eventName":"__pest_evaluable_the_dashboard_invites_setting_a_target_when_none_exists","line":172}],"name":"SalesTargetTest","directories":["Dashboard"]},{"path":"tests\/Feature\/Deals\/ConvertToDealTest.php","tests":[{"name":"converting an approved quotation creates a deal with copied items and marks the quotation converted","eventName":"__pest_evaluable_converting_an_approved_quotation_creates_a_deal_with_copied_items_and_marks_the_quotation_converted","line":47},{"name":"a quotation cannot be converted twice (idempotent)","eventName":"__pest_evaluable_a_quotation_cannot_be_converted_twice__idempotent_","line":63},{"name":"a draft or unapproved quotation cannot be converted","eventName":"__pest_evaluable_a_draft_or_unapproved_quotation_cannot_be_converted","line":74},{"name":"converting is audited","eventName":"__pest_evaluable_converting_is_audited","line":93}],"name":"ConvertToDealTest","directories":["Deals"]},{"path":"tests\/Feature\/Deals\/DealStateMachineTest.php","tests":[{"name":"marking a deal won transitions status and moves it to the won stage","eventName":"__pest_evaluable_marking_a_deal_won_transitions_status_and_moves_it_to_the_won_stage","line":40},{"name":"marking a deal lost transitions status and moves it to the lost stage","eventName":"__pest_evaluable_marking_a_deal_lost_transitions_status_and_moves_it_to_the_lost_stage","line":50},{"name":"a won deal cannot be marked lost afterward (terminal state)","eventName":"__pest_evaluable_a_won_deal_cannot_be_marked_lost_afterward__terminal_state_","line":61},{"name":"moving a deal to a stage from a different pipeline is rejected","eventName":"__pest_evaluable_moving_a_deal_to_a_stage_from_a_different_pipeline_is_rejected","line":70},{"name":"a stale optimistic lock version is rejected on concurrent update","eventName":"__pest_evaluable_a_stale_optimistic_lock_version_is_rejected_on_concurrent_update","line":80},{"name":"workspace A cannot see workspace B deals","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_deals","line":93}],"name":"DealStateMachineTest","directories":["Deals"]},{"path":"tests\/Feature\/Deals\/IssueInvoiceTest.php","tests":[{"name":"issuing an invoice for a won deal assigns a tracking number","eventName":"__pest_evaluable_issuing_an_invoice_for_a_won_deal_assigns_a_tracking_number","line":11},{"name":"an invoice cannot be issued for a deal that is not won","eventName":"__pest_evaluable_an_invoice_cannot_be_issued_for_a_deal_that_is_not_won","line":24},{"name":"an invoice cannot be issued twice for the same deal","eventName":"__pest_evaluable_an_invoice_cannot_be_issued_twice_for_the_same_deal","line":35},{"name":"a seller cannot issue an invoice for another seller's deal","eventName":"__pest_evaluable_a_seller_cannot_issue_an_invoice_for_another_seller_s_deal","line":47},{"name":"issuing an invoice emails every portal user of the company with financial access","eventName":"__pest_evaluable_issuing_an_invoice_emails_every_portal_user_of_the_company_with_financial_access","line":62}],"name":"IssueInvoiceTest","directories":["Deals"]},{"path":"tests\/Feature\/Deals\/PaymentEditTest.php","tests":[{"name":"a note recorded with a payment is stored and shown on the deal page","eventName":"__pest_evaluable_a_note_recorded_with_a_payment_is_stored_and_shown_on_the_deal_page","line":52},{"name":"editing a pending payment changes its fields","eventName":"__pest_evaluable_editing_a_pending_payment_changes_its_fields","line":68},{"name":"editing a confirmed payment re-derives the deal totals","eventName":"__pest_evaluable_editing_a_confirmed_payment_re_derives_the_deal_totals","line":89},{"name":"status is not editable through the update path","eventName":"__pest_evaluable_status_is_not_editable_through_the_update_path","line":111},{"name":"a reversed payment cannot be edited","eventName":"__pest_evaluable_a_reversed_payment_cannot_be_edited","line":132},{"name":"a seller cannot edit a payment","eventName":"__pest_evaluable_a_seller_cannot_edit_a_payment","line":146},{"name":"the update permission is granted to exactly the roles that can create payments","eventName":"__pest_evaluable_the_update_permission_is_granted_to_exactly_the_roles_that_can_create_payments","line":163},{"name":"a file attached to a payment is stored and downloadable from the deal page","eventName":"__pest_evaluable_a_file_attached_to_a_payment_is_stored_and_downloadable_from_the_deal_page","line":174},{"name":"a payment saves fine with no file attached","eventName":"__pest_evaluable_a_payment_saves_fine_with_no_file_attached","line":196},{"name":"the edit modal loads the existing payment into the form","eventName":"__pest_evaluable_the_edit_modal_loads_the_existing_payment_into_the_form","line":211},{"name":"saving an edit through the page updates rather than creating a second payment","eventName":"__pest_evaluable_saving_an_edit_through_the_page_updates_rather_than_creating_a_second_payment","line":230},{"name":"an oversized file is rejected before any payment is written","eventName":"__pest_evaluable_an_oversized_file_is_rejected_before_any_payment_is_written","line":251},{"name":"the date picker re-syncs from the hidden field when a modal is populated later","eventName":"__pest_evaluable_the_date_picker_re_syncs_from_the_hidden_field_when_a_modal_is_populated_later","line":265},{"name":"the payment modal keys its file and date inputs so both reset between opens","eventName":"__pest_evaluable_the_payment_modal_keys_its_file_and_date_inputs_so_both_reset_between_opens","line":283},{"name":"opening the form for a new payment clears a previously edited one","eventName":"__pest_evaluable_opening_the_form_for_a_new_payment_clears_a_previously_edited_one","line":290}],"name":"PaymentEditTest","directories":["Deals"]},{"path":"tests\/Feature\/Deals\/PaymentTest.php","tests":[{"name":"a partial confirmed payment correctly sets the deal to partial with the right outstanding amount","eventName":"__pest_evaluable_a_partial_confirmed_payment_correctly_sets_the_deal_to_partial_with_the_right_outstanding_amount","line":39},{"name":"a pending payment does not affect the deal payment status until confirmed","eventName":"__pest_evaluable_a_pending_payment_does_not_affect_the_deal_payment_status_until_confirmed","line":56},{"name":"fully confirmed payments mark the deal as paid","eventName":"__pest_evaluable_fully_confirmed_payments_mark_the_deal_as_paid","line":70},{"name":"reversing a confirmed payment recomputes the deal back to unpaid","eventName":"__pest_evaluable_reversing_a_confirmed_payment_recomputes_the_deal_back_to_unpaid","line":84},{"name":"a pending payment cannot be reversed directly (must be confirmed first)","eventName":"__pest_evaluable_a_pending_payment_cannot_be_reversed_directly__must_be_confirmed_first_","line":104},{"name":"a confirmed payment cannot be confirmed twice","eventName":"__pest_evaluable_a_confirmed_payment_cannot_be_confirmed_twice","line":117}],"name":"PaymentTest","directories":["Deals"]},{"path":"tests\/Feature\/Deals\/SellerDealPermissionsTest.php","tests":[{"name":"a seller can move their own deal to a new stage","eventName":"__pest_evaluable_a_seller_can_move_their_own_deal_to_a_new_stage","line":49},{"name":"a seller can mark their own deal as won","eventName":"__pest_evaluable_a_seller_can_mark_their_own_deal_as_won","line":57},{"name":"a seller cannot move a deal belonging to another seller","eventName":"__pest_evaluable_a_seller_cannot_move_a_deal_belonging_to_another_seller","line":66},{"name":"finance can view deals created by another workspace member","eventName":"__pest_evaluable_finance_can_view_deals_created_by_another_workspace_member","line":77}],"name":"SellerDealPermissionsTest","directories":["Deals"]},{"path":"tests\/Feature\/FontTest.php","tests":[{"name":"all five declared weights exist on disk","eventName":"__pest_evaluable_all_five_declared_weights_exist_on_disk","line":8},{"name":"the stylesheet declares every weight with a woff2 and a woff source","eventName":"__pest_evaluable_the_stylesheet_declares_every_weight_with_a_woff2_and_a_woff_source","line":27},{"name":"tailwind resolves the sans stack to the new family","eventName":"__pest_evaluable_tailwind_resolves_the_sans_stack_to_the_new_family","line":41},{"name":"no page still references the old font","eventName":"__pest_evaluable_no_page_still_references_the_old_font","line":48},{"name":"the regular weight is preloaded on the marketing pages","eventName":"__pest_evaluable_the_regular_weight_is_preloaded_on_the_marketing_pages","line":55},{"name":"the font is served from this host, never a CDN","eventName":"__pest_evaluable_the_font_is_served_from_this_host__never_a_CDN","line":62},{"name":"PDF rendering still uses the TTF font it needs","eventName":"__pest_evaluable_PDF_rendering_still_uses_the_TTF_font_it_needs","line":72}],"name":"FontTest","directories":[]},{"path":"tests\/Feature\/GuidePageTest.php","tests":[{"name":"the guide page lists every system section and is reachable without logging in","eventName":"__pest_evaluable_the_guide_page_lists_every_system_section_and_is_reachable_without_logging_in","line":3},{"name":"the landing page links to the guide page","eventName":"__pest_evaluable_the_landing_page_links_to_the_guide_page","line":13}],"name":"GuidePageTest","directories":[]},{"path":"tests\/Feature\/LandingPageTest.php","tests":[{"name":"the homepage renders for a logged-in portal user","eventName":"__pest_evaluable_the_homepage_renders_for_a_logged_in_portal_user","line":12}],"name":"LandingPageTest","directories":[]},{"path":"tests\/Feature\/Layout\/GlobalSearchTest.php","tests":[{"name":"searching finds a customer by name and a partner company by name","eventName":"__pest_evaluable_searching_finds_a_customer_by_name_and_a_partner_company_by_name","line":10},{"name":"searching finds a project by name","eventName":"__pest_evaluable_searching_finds_a_project_by_name","line":28},{"name":"a short query does not open the results panel","eventName":"__pest_evaluable_a_short_query_does_not_open_the_results_panel","line":43},{"name":"a seller only finds their own private customers, matching the customers list visibility rule","eventName":"__pest_evaluable_a_seller_only_finds_their_own_private_customers__matching_the_customers_list_visibility_rule","line":49},{"name":"workspace A cannot find workspace B records through global search","eventName":"__pest_evaluable_workspace_A_cannot_find_workspace_B_records_through_global_search","line":67}],"name":"GlobalSearchTest","directories":["Layout"]},{"path":"tests\/Feature\/Layout\/MobileNavigationTest.php","tests":[{"name":"the off-canvas drawer is a sibling of the blurred bar, not inside it","eventName":"__pest_evaluable_the_off_canvas_drawer_is_a_sibling_of_the_blurred_bar__not_inside_it","line":15},{"name":"the Alpine state sits on the wrapper so bar and drawer share it","eventName":"__pest_evaluable_the_Alpine_state_sits_on_the_wrapper_so_bar_and_drawer_share_it","line":35},{"name":"the drawer sits above both the top bar and the bottom bar","eventName":"__pest_evaluable_the_drawer_sits_above_both_the_top_bar_and_the_bottom_bar","line":48},{"name":"the cramped quick-create button is hidden on mobile but kept on desktop","eventName":"__pest_evaluable_the_cramped_quick_create_button_is_hidden_on_mobile_but_kept_on_desktop","line":57},{"name":"the bottom bar carries a circular action button instead","eventName":"__pest_evaluable_the_bottom_bar_carries_a_circular_action_button_instead","line":64},{"name":"the action button opens the same drawer as the desktop one","eventName":"__pest_evaluable_the_action_button_opens_the_same_drawer_as_the_desktop_one","line":74},{"name":"every bottom-bar destination has an icon and a label","eventName":"__pest_evaluable_every_bottom_bar_destination_has_an_icon_and_a_label","line":83},{"name":"bottom-bar targets clear the 44px tap minimum","eventName":"__pest_evaluable_bottom_bar_targets_clear_the_44px_tap_minimum","line":94},{"name":"page content reserves room for the taller bar and the notch","eventName":"__pest_evaluable_page_content_reserves_room_for_the_taller_bar_and_the_notch","line":102},{"name":"the bottom bar is mobile-only and the sidebar desktop-only","eventName":"__pest_evaluable_the_bottom_bar_is_mobile_only_and_the_sidebar_desktop_only","line":110},{"name":"the drawer carries a workspace switcher, since the top-bar one is desktop-only","eventName":"__pest_evaluable_the_drawer_carries_a_workspace_switcher__since_the_top_bar_one_is_desktop_only","line":118},{"name":"exactly one switcher is reachable at any width","eventName":"__pest_evaluable_exactly_one_switcher_is_reachable_at_any_width","line":130},{"name":"the drawer accordion does not shadow the drawer state","eventName":"__pest_evaluable_the_drawer_accordion_does_not_shadow_the_drawer_state","line":139},{"name":"the drawer lists every workspace the user belongs to","eventName":"__pest_evaluable_the_drawer_lists_every_workspace_the_user_belongs_to","line":147},{"name":"the dashboard still renders with the reworked chrome","eventName":"__pest_evaluable_the_dashboard_still_renders_with_the_reworked_chrome","line":170}],"name":"MobileNavigationTest","directories":["Layout"]},{"path":"tests\/Feature\/Layout\/ResponsiveAndTourTest.php","tests":[{"name":"dashboard panels can shrink below their content so tables scroll instead of the page","eventName":"__pest_evaluable_dashboard_panels_can_shrink_below_their_content_so_tables_scroll_instead_of_the_page","line":13},{"name":"every wide table on the analytics page is wrapped in its own scroller","eventName":"__pest_evaluable_every_wide_table_on_the_analytics_page_is_wrapped_in_its_own_scroller","line":21},{"name":"the customer page header wraps instead of squeezing its actions","eventName":"__pest_evaluable_the_customer_page_header_wraps_instead_of_squeezing_its_actions","line":31},{"name":"the customer page follows the theme tokens rather than fixed greys","eventName":"__pest_evaluable_the_customer_page_follows_the_theme_tokens_rather_than_fixed_greys","line":39},{"name":"the customer page renders for a permitted member","eventName":"__pest_evaluable_the_customer_page_renders_for_a_permitted_member","line":49},{"name":"the tour only anchors to targets that are actually laid out","eventName":"__pest_evaluable_the_tour_only_anchors_to_targets_that_are_actually_laid_out","line":56},{"name":"the tour resolves each selector to the visible copy, not the first in the DOM","eventName":"__pest_evaluable_the_tour_resolves_each_selector_to_the_visible_copy__not_the_first_in_the_DOM","line":67},{"name":"the tour popover keeps the brand font despite driver.js own stylesheet","eventName":"__pest_evaluable_the_tour_popover_keeps_the_brand_font_despite_driver_js_own_stylesheet","line":77},{"name":"the tour popover is laid out right-to-left","eventName":"__pest_evaluable_the_tour_popover_is_laid_out_right_to_left","line":89}],"name":"ResponsiveAndTourTest","directories":["Layout"]},{"path":"tests\/Feature\/LivewirePersistentMiddlewareTest.php","tests":[{"name":"workspace and portal context middleware are registered as Livewire persistent middleware","eventName":"__pest_evaluable_workspace_and_portal_context_middleware_are_registered_as_Livewire_persistent_middleware","line":22}],"name":"LivewirePersistentMiddlewareTest","directories":[]},{"path":"tests\/Feature\/Marketing\/BlogRoutesTest.php","tests":[{"name":"blog posts are not workspace-scoped","eventName":"__pest_evaluable_blog_posts_are_not_workspace_scoped","line":12},{"name":"the public blog index renders with no workspace context","eventName":"__pest_evaluable_the_public_blog_index_renders_with_no_workspace_context","line":17},{"name":"a published post renders on its own page","eventName":"__pest_evaluable_a_published_post_renders_on_its_own_page","line":31},{"name":"a draft post is not reachable publicly","eventName":"__pest_evaluable_a_draft_post_is_not_reachable_publicly","line":45},{"name":"the admin blog screen renders for a platform admin with no workspace","eventName":"__pest_evaluable_the_admin_blog_screen_renders_for_a_platform_admin_with_no_workspace","line":57},{"name":"a post can be created from the admin screen without a workspace","eventName":"__pest_evaluable_a_post_can_be_created_from_the_admin_screen_without_a_workspace","line":66}],"name":"BlogRoutesTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/FaqTest.php","tests":[{"name":"every question the brief listed is present in the FAQ","eventName":"__pest_evaluable_every_question_the_brief_listed_is_present_in_the_FAQ","line":5},{"name":"the homepage renders every answered question and no unanswered one","eventName":"__pest_evaluable_the_homepage_renders_every_answered_question_and_no_unanswered_one","line":28},{"name":"FAQ entries are grouped and every group is rendered","eventName":"__pest_evaluable_FAQ_entries_are_grouped_and_every_group_is_rendered","line":44},{"name":"FAQPage schema contains exactly the answered questions","eventName":"__pest_evaluable_FAQPage_schema_contains_exactly_the_answered_questions","line":57},{"name":"no answered FAQ contradicts the published pricing plans","eventName":"__pest_evaluable_no_answered_FAQ_contradicts_the_published_pricing_plans","line":83}],"name":"FaqTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/FaviconTest.php","tests":[{"name":"the brand icon is declared on every layout a visitor can reach","eventName":"__pest_evaluable_the_brand_icon_is_declared_on_every_layout_a_visitor_can_reach","line":3},{"name":"every layout includes the shared favicon partial exactly once","eventName":"__pest_evaluable_every_layout_includes_the_shared_favicon_partial_exactly_once","line":22},{"name":"the svg brand mark is a real, parseable svg","eventName":"__pest_evaluable_the_svg_brand_mark_is_a_real__parseable_svg","line":42},{"name":"no layout links an icon file that is missing or empty","eventName":"__pest_evaluable_no_layout_links_an_icon_file_that_is_missing_or_empty","line":59},{"name":"the PWA icons show the brand mark, not the pre-rebrand placeholder","eventName":"__pest_evaluable_the_PWA_icons_show_the_brand_mark__not_the_pre_rebrand_placeholder","line":75},{"name":"the apple touch icon is opaque, since iOS squares off transparency to black","eventName":"__pest_evaluable_the_apple_touch_icon_is_opaque__since_iOS_squares_off_transparency_to_black","line":91},{"name":"the manifest offers a maskable icon for adaptive launchers","eventName":"__pest_evaluable_the_manifest_offers_a_maskable_icon_for_adaptive_launchers","line":101},{"name":"the manifest is linked exactly once per page","eventName":"__pest_evaluable_the_manifest_is_linked_exactly_once_per_page","line":110},{"name":"the web manifest carries the brand identity","eventName":"__pest_evaluable_the_web_manifest_carries_the_brand_identity","line":117}],"name":"FaviconTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/FooterAndSecurityTest.php","tests":[{"name":"the footer never renders a link to a route that does not exist","eventName":"__pest_evaluable_the_footer_never_renders_a_link_to_a_route_that_does_not_exist","line":6},{"name":"the footer links to the pages that already exist","eventName":"__pest_evaluable_the_footer_links_to_the_pages_that_already_exist","line":27},{"name":"the footer invents no contact details","eventName":"__pest_evaluable_the_footer_invents_no_contact_details","line":41},{"name":"the security page states only what the code enforces","eventName":"__pest_evaluable_the_security_page_states_only_what_the_code_enforces","line":52},{"name":"undecided security topics are left off the page entirely","eventName":"__pest_evaluable_undecided_security_topics_are_left_off_the_page_entirely","line":68},{"name":"the page renders every verified topic and nothing else","eventName":"__pest_evaluable_the_page_renders_every_verified_topic_and_nothing_else","line":85},{"name":"the security page claims nothing that cannot be verified from the repository","eventName":"__pest_evaluable_the_security_page_claims_nothing_that_cannot_be_verified_from_the_repository","line":95},{"name":"the security page is public and carries canonical and breadcrumbs","eventName":"__pest_evaluable_the_security_page_is_public_and_carries_canonical_and_breadcrumbs","line":106}],"name":"FooterAndSecurityTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/GuidePageTest.php","tests":[{"name":"the guide shows the wordmark, not the app launcher icon","eventName":"__pest_evaluable_the_guide_shows_the_wordmark__not_the_app_launcher_icon","line":3},{"name":"the guide uses the brand palette rather than default greys","eventName":"__pest_evaluable_the_guide_uses_the_brand_palette_rather_than_default_greys","line":17},{"name":"every section in the sidebar has a matching anchor on the page","eventName":"__pest_evaluable_every_section_in_the_sidebar_has_a_matching_anchor_on_the_page","line":30},{"name":"anchored sections leave room so a heading is not flush against the top","eventName":"__pest_evaluable_anchored_sections_leave_room_so_a_heading_is_not_flush_against_the_top","line":41},{"name":"the guide documents the reporting features, not just the older modules","eventName":"__pest_evaluable_the_guide_documents_the_reporting_features__not_just_the_older_modules","line":48},{"name":"the guide names the behaviours a reader would otherwise call a bug","eventName":"__pest_evaluable_the_guide_names_the_behaviours_a_reader_would_otherwise_call_a_bug","line":61},{"name":"the guide ends with a way onward instead of stopping dead","eventName":"__pest_evaluable_the_guide_ends_with_a_way_onward_instead_of_stopping_dead","line":70},{"name":"the guide is public and carries the brand font and icons","eventName":"__pest_evaluable_the_guide_is_public_and_carries_the_brand_font_and_icons","line":77}],"name":"GuidePageTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/LimocPageTest.php","tests":[{"name":"the limoc marketing page can be used as the homepage","eventName":"__pest_evaluable_the_limoc_marketing_page_can_be_used_as_the_homepage","line":8},{"name":"the limoc marketing page is reachable without logging in","eventName":"__pest_evaluable_the_limoc_marketing_page_is_reachable_without_logging_in","line":16},{"name":"the homepage leads with the new positioning and the full customer journey","eventName":"__pest_evaluable_the_homepage_leads_with_the_new_positioning_and_the_full_customer_journey","line":29},{"name":"the homepage states the problems and then the benefits of solving them","eventName":"__pest_evaluable_the_homepage_states_the_problems_and_then_the_benefits_of_solving_them","line":45},{"name":"capability links stay hidden until their landing page exists","eventName":"__pest_evaluable_capability_links_stay_hidden_until_their_landing_page_exists","line":66},{"name":"the product screenshot section offers a tab for every module","eventName":"__pest_evaluable_the_product_screenshot_section_offers_a_tab_for_every_module","line":77},{"name":"a screenshot renders as an image only once the file is really on disk","eventName":"__pest_evaluable_a_screenshot_renders_as_an_image_only_once_the_file_is_really_on_disk","line":89},{"name":"the screenshot placeholder is shown for exactly the captures that are missing","eventName":"__pest_evaluable_the_screenshot_placeholder_is_shown_for_exactly_the_captures_that_are_missing","line":107},{"name":"the quotation section explains the full online approval flow","eventName":"__pest_evaluable_the_quotation_section_explains_the_full_online_approval_flow","line":120},{"name":"the portal section lists only the pages a portal user really has","eventName":"__pest_evaluable_the_portal_section_lists_only_the_pages_a_portal_user_really_has","line":133},{"name":"every advertised SLA control maps to something the code actually does","eventName":"__pest_evaluable_every_advertised_SLA_control_maps_to_something_the_code_actually_does","line":148},{"name":"the homepage never advertises a capability the product does not have","eventName":"__pest_evaluable_the_homepage_never_advertises_a_capability_the_product_does_not_have","line":165},{"name":"submitting the trial request form stores the lead and shows a success message","eventName":"__pest_evaluable_submitting_the_trial_request_form_stores_the_lead_and_shows_a_success_message","line":174},{"name":"the trial request form rejects an invalid mobile number and an unchecked agreement","eventName":"__pest_evaluable_the_trial_request_form_rejects_an_invalid_mobile_number_and_an_unchecked_agreement","line":189}],"name":"LimocPageTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/PerformanceTest.php","tests":[{"name":"marketing pages do not load the application javascript bundle","eventName":"__pest_evaluable_marketing_pages_do_not_load_the_application_javascript_bundle","line":5},{"name":"the header logo is served at a size proportional to how it is displayed","eventName":"__pest_evaluable_the_header_logo_is_served_at_a_size_proportional_to_how_it_is_displayed","line":27},{"name":"images below the fold are lazy loaded and carry explicit dimensions","eventName":"__pest_evaluable_images_below_the_fold_are_lazy_loaded_and_carry_explicit_dimensions","line":47},{"name":"the font is self-hosted and preloaded, with no external requests anywhere","eventName":"__pest_evaluable_the_font_is_self_hosted_and_preloaded__with_no_external_requests_anywhere","line":65},{"name":"the screenshot switcher keeps a fixed aspect ratio so tabs cannot shift layout","eventName":"__pest_evaluable_the_screenshot_switcher_keeps_a_fixed_aspect_ratio_so_tabs_cannot_shift_layout","line":93}],"name":"PerformanceTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/PricingPageTest.php","tests":[{"name":"the pricing page lists every plan with its own user band","eventName":"__pest_evaluable_the_pricing_page_lists_every_plan_with_its_own_user_band","line":5},{"name":"the plan user bands do not overlap","eventName":"__pest_evaluable_the_plan_user_bands_do_not_overlap","line":16},{"name":"the \"all features in every plan\" claim holds against the codebase","eventName":"__pest_evaluable_the__all_features_in_every_plan__claim_holds_against_the_codebase","line":24},{"name":"unknown commercial terms are shown as pending, never invented","eventName":"__pest_evaluable_unknown_commercial_terms_are_shown_as_pending__never_invented","line":35},{"name":"every pricing term the brief asked about is addressed on the page","eventName":"__pest_evaluable_every_pricing_term_the_brief_asked_about_is_addressed_on_the_page","line":54},{"name":"the pricing page carries canonical, breadcrumbs and FAQ schema","eventName":"__pest_evaluable_the_pricing_page_carries_canonical__breadcrumbs_and_FAQ_schema","line":72},{"name":"the homepage pricing block agrees with the pricing page","eventName":"__pest_evaluable_the_homepage_pricing_block_agrees_with_the_pricing_page","line":80}],"name":"PricingPageTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/ResponsiveTest.php","tests":[{"name":"interactive controls carry the 44px minimum tap target","eventName":"__pest_evaluable_interactive_controls_carry_the_44px_minimum_tap_target","line":9},{"name":"the FAQ padding sits on the summary so the whole row toggles","eventName":"__pest_evaluable_the_FAQ_padding_sits_on_the_summary_so_the_whole_row_toggles","line":30},{"name":"the mobile sticky CTA is balanced by a spacer so it cannot cover the footer","eventName":"__pest_evaluable_the_mobile_sticky_CTA_is_balanced_by_a_spacer_so_it_cannot_cover_the_footer","line":38},{"name":"every marketing page declares the mobile viewport and RTL direction","eventName":"__pest_evaluable_every_marketing_page_declares_the_mobile_viewport_and_RTL_direction","line":46},{"name":"entrance animations are disabled for visitors who ask for reduced motion","eventName":"__pest_evaluable_entrance_animations_are_disabled_for_visitors_who_ask_for_reduced_motion","line":59},{"name":"the layout never relies on JavaScript to reveal content","eventName":"__pest_evaluable_the_layout_never_relies_on_JavaScript_to_reveal_content","line":68}],"name":"ResponsiveTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/SeoTest.php","tests":[{"name":"every marketing page has exactly one h1","eventName":"__pest_evaluable_every_marketing_page_has_exactly_one_h1","line":22},{"name":"every marketing page has a canonical url matching the page itself","eventName":"__pest_evaluable_every_marketing_page_has_a_canonical_url_matching_the_page_itself","line":28},{"name":"every marketing page carries Open Graph and Twitter card tags","eventName":"__pest_evaluable_every_marketing_page_carries_Open_Graph_and_Twitter_card_tags","line":37},{"name":"page titles and descriptions are unique across the site","eventName":"__pest_evaluable_page_titles_and_descriptions_are_unique_across_the_site","line":47},{"name":"every image on a marketing page has an alt attribute","eventName":"__pest_evaluable_every_image_on_a_marketing_page_has_an_alt_attribute","line":70},{"name":"the sitemap lists every public marketing page and nothing private","eventName":"__pest_evaluable_the_sitemap_lists_every_public_marketing_page_and_nothing_private","line":80},{"name":"the sitemap is valid, parseable xml","eventName":"__pest_evaluable_the_sitemap_is_valid__parseable_xml","line":102},{"name":"robots.txt blocks the private areas and points at the sitemap","eventName":"__pest_evaluable_robots_txt_blocks_the_private_areas_and_points_at_the_sitemap","line":115},{"name":"the public quotation document is never indexable","eventName":"__pest_evaluable_the_public_quotation_document_is_never_indexable","line":125},{"name":"marketing pages internally link onward rather than dead-ending","eventName":"__pest_evaluable_marketing_pages_internally_link_onward_rather_than_dead_ending","line":135}],"name":"SeoTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/SocialProofTest.php","tests":[{"name":"no social proof is published while there is no real customer material","eventName":"__pest_evaluable_no_social_proof_is_published_while_there_is_no_real_customer_material","line":5},{"name":"the homepage hides the social proof sections entirely while they are empty","eventName":"__pest_evaluable_the_homepage_hides_the_social_proof_sections_entirely_while_they_are_empty","line":15},{"name":"the section appears as soon as real material is supplied","eventName":"__pest_evaluable_the_section_appears_as_soon_as_real_material_is_supplied","line":30},{"name":"a case study renders the before, usage and result beats","eventName":"__pest_evaluable_a_case_study_renders_the_before__usage_and_result_beats","line":46}],"name":"SocialProofTest","directories":["Marketing"]},{"path":"tests\/Feature\/Marketing\/SolutionPagesTest.php","tests":[{"name":"the solutions index lists every industry and links to its page","eventName":"__pest_evaluable_the_solutions_index_lists_every_industry_and_links_to_its_page","line":5},{"name":"every industry page renders with its own title, description and heading","eventName":"__pest_evaluable_every_industry_page_renders_with_its_own_title__description_and_heading","line":15},{"name":"every industry page states that industry challenges and how limoc answers them","eventName":"__pest_evaluable_every_industry_page_states_that_industry_challenges_and_how_limoc_answers_them","line":26},{"name":"every industry page carries a canonical url and matching breadcrumbs","eventName":"__pest_evaluable_every_industry_page_carries_a_canonical_url_and_matching_breadcrumbs","line":43},{"name":"every industry page cross-links to sibling industries","eventName":"__pest_evaluable_every_industry_page_cross_links_to_sibling_industries","line":54},{"name":"an unknown industry slug returns 404 rather than an empty page","eventName":"__pest_evaluable_an_unknown_industry_slug_returns_404_rather_than_an_empty_page","line":71},{"name":"the homepage industry cards link through to the solution pages","eventName":"__pest_evaluable_the_homepage_industry_cards_link_through_to_the_solution_pages","line":75},{"name":"solution pages are reachable without authentication","eventName":"__pest_evaluable_solution_pages_are_reachable_without_authentication","line":88}],"name":"SolutionPagesTest","directories":["Marketing"]},{"path":"tests\/Feature\/Notifications\/BellComponentTest.php","tests":[{"name":"the bell renders normally with an active workspace context","eventName":"__pest_evaluable_the_bell_renders_normally_with_an_active_workspace_context","line":8},{"name":"the bell renders an empty state instead of throwing when the workspace context is missing","eventName":"__pest_evaluable_the_bell_renders_an_empty_state_instead_of_throwing_when_the_workspace_context_is_missing","line":25}],"name":"BellComponentTest","directories":["Notifications"]},{"path":"tests\/Feature\/Notifications\/NotificationTest.php","tests":[{"name":"NotificationService creates a persistent app_notifications row for the recipient","eventName":"__pest_evaluable_NotificationService_creates_a_persistent_app__notifications_row_for_the_recipient","line":14},{"name":"approving a commission entry notifies its beneficiary","eventName":"__pest_evaluable_approving_a_commission_entry_notifies_its_beneficiary","line":28},{"name":"a user only sees their own notifications","eventName":"__pest_evaluable_a_user_only_sees_their_own_notifications","line":45},{"name":"markAsRead only sets read_at once","eventName":"__pest_evaluable_markAsRead_only_sets_read__at_once","line":60},{"name":"subscribing twice with the same endpoint updates the existing row instead of duplicating","eventName":"__pest_evaluable_subscribing_twice_with_the_same_endpoint_updates_the_existing_row_instead_of_duplicating","line":74},{"name":"unsubscribing removes the subscription","eventName":"__pest_evaluable_unsubscribing_removes_the_subscription","line":90},{"name":"workspace A cannot see workspace B notifications through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_notifications_through_the_global_scope","line":104}],"name":"NotificationTest","directories":["Notifications"]},{"path":"tests\/Feature\/Offline\/SyncControllerTest.php","tests":[{"name":"syncing a customer draft creates the customer and is idempotent on repeat","eventName":"__pest_evaluable_syncing_a_customer_draft_creates_the_customer_and_is_idempotent_on_repeat","line":7},{"name":"syncing a company-type customer draft persists its legal fields","eventName":"__pest_evaluable_syncing_a_company_type_customer_draft_persists_its_legal_fields","line":30},{"name":"syncing an activity draft for an existing customer succeeds","eventName":"__pest_evaluable_syncing_an_activity_draft_for_an_existing_customer_succeeds","line":60},{"name":"syncing an activity for a not-yet-synced customer returns a conflict","eventName":"__pest_evaluable_syncing_an_activity_for_a_not_yet_synced_customer_returns_a_conflict","line":78},{"name":"OfflineReferenceDataController returns visible customers and active projects","eventName":"__pest_evaluable_OfflineReferenceDataController_returns_visible_customers_and_active_projects","line":93}],"name":"SyncControllerTest","directories":["Offline"]},{"path":"tests\/Feature\/Onboarding\/TourLauncherTest.php","tests":[{"name":"a first-time user auto-starts the tour and it is not shown again after being marked seen","eventName":"__pest_evaluable_a_first_time_user_auto_starts_the_tour_and_it_is_not_shown_again_after_being_marked_seen","line":6},{"name":"marking one tour seen does not affect another tour for the same user","eventName":"__pest_evaluable_marking_one_tour_seen_does_not_affect_another_tour_for_the_same_user","line":25}],"name":"TourLauncherTest","directories":["Onboarding"]},{"path":"tests\/Feature\/PartnerCompanies\/PartnerCompanyAuthorizationTest.php","tests":[{"name":"the owner can create a partner company and it is audited","eventName":"__pest_evaluable_the_owner_can_create_a_partner_company_and_it_is_audited","line":8},{"name":"a seller can create and update partner companies (policy)","eventName":"__pest_evaluable_a_seller_can_create_and_update_partner_companies__policy_","line":23},{"name":"a sales manager can view but not create partner companies","eventName":"__pest_evaluable_a_sales_manager_can_view_but_not_create_partner_companies","line":34}],"name":"PartnerCompanyAuthorizationTest","directories":["PartnerCompanies"]},{"path":"tests\/Feature\/PartnerCompanies\/PartnerCompanyContractStatusTest.php","tests":[{"name":"a new partner company defaults to no contract but the contract status can be chosen at creation","eventName":"__pest_evaluable_a_new_partner_company_defaults_to_no_contract_but_the_contract_status_can_be_chosen_at_creation","line":8},{"name":"the contract status of an existing partner company can be changed from the edit form","eventName":"__pest_evaluable_the_contract_status_of_an_existing_partner_company_can_be_changed_from_the_edit_form","line":28},{"name":"an unknown partner company contract status is rejected","eventName":"__pest_evaluable_an_unknown_partner_company_contract_status_is_rejected","line":45}],"name":"PartnerCompanyContractStatusTest","directories":["PartnerCompanies"]},{"path":"tests\/Feature\/PartnerCompanies\/PortalUsersTabTest.php","tests":[{"name":"a seller cannot invite a portal user (no PortalUsersManage permission)","eventName":"__pest_evaluable_a_seller_cannot_invite_a_portal_user__no_PortalUsersManage_permission_","line":10},{"name":"a sales manager can invite a portal user","eventName":"__pest_evaluable_a_sales_manager_can_invite_a_portal_user","line":27}],"name":"PortalUsersTabTest","directories":["PartnerCompanies"]},{"path":"tests\/Feature\/Portal\/LogoutButtonTest.php","tests":[{"name":"a portal user can log out through the logout-button component","eventName":"__pest_evaluable_a_portal_user_can_log_out_through_the_logout_button_component","line":8}],"name":"LogoutButtonTest","directories":["Portal"]},{"path":"tests\/Feature\/Portal\/PortalAccessScopingTest.php","tests":[{"name":"a portal user of company A cannot be treated as authorized for company B","eventName":"__pest_evaluable_a_portal_user_of_company_A_cannot_be_treated_as_authorized_for_company_B","line":6},{"name":"with no PortalContext established, nothing is viewable","eventName":"__pest_evaluable_with_no_PortalContext_established__nothing_is_viewable","line":22}],"name":"PortalAccessScopingTest","directories":["Portal"]},{"path":"tests\/Feature\/Portal\/PortalDashboardTest.php","tests":[{"name":"portal dashboard summarizes only the current company and accessible projects","eventName":"__pest_evaluable_portal_dashboard_summarizes_only_the_current_company_and_accessible_projects","line":14}],"name":"PortalDashboardTest","directories":["Portal"]},{"path":"tests\/Feature\/Portal\/PortalFinancialPagesTest.php","tests":[{"name":"a portal user only sees quotations belonging to their own company and project","eventName":"__pest_evaluable_a_portal_user_only_sees_quotations_belonging_to_their_own_company_and_project","line":15},{"name":"a portal user can approve their own quotation through the portal show page","eventName":"__pest_evaluable_a_portal_user_can_approve_their_own_quotation_through_the_portal_show_page","line":34},{"name":"the invoices page is forbidden without can_view_financial","eventName":"__pest_evaluable_the_invoices_page_is_forbidden_without_can__view__financial","line":57},{"name":"the invoices page only lists deals that have been issued an invoice number, scoped to the company","eventName":"__pest_evaluable_the_invoices_page_only_lists_deals_that_have_been_issued_an_invoice_number__scoped_to_the_company","line":70},{"name":"the contracts page is forbidden without can_view_contracts","eventName":"__pest_evaluable_the_contracts_page_is_forbidden_without_can__view__contracts","line":98},{"name":"draft contracts are never shown on the portal contracts page","eventName":"__pest_evaluable_draft_contracts_are_never_shown_on_the_portal_contracts_page","line":111}],"name":"PortalFinancialPagesTest","directories":["Portal"]},{"path":"tests\/Feature\/Portal\/PortalInvitationTest.php","tests":[{"name":"inviting a brand-new email creates a pending invitation and sends a notification, no user yet","eventName":"__pest_evaluable_inviting_a_brand_new_email_creates_a_pending_invitation_and_sends_a_notification__no_user_yet","line":25},{"name":"accepting a pending invitation via its plain token creates exactly one portal user with the right access flags","eventName":"__pest_evaluable_accepting_a_pending_invitation_via_its_plain_token_creates_exactly_one_portal_user_with_the_right_access_flags","line":42},{"name":"inviting an existing portal user email just adds access, no notification, no duplicate account","eventName":"__pest_evaluable_inviting_an_existing_portal_user_email_just_adds_access__no_notification__no_duplicate_account","line":69},{"name":"inviting a staff account email is rejected","eventName":"__pest_evaluable_inviting_a_staff_account_email_is_rejected","line":90},{"name":"accepting an invitation gives the portal user the mobile number the invitation carried","eventName":"__pest_evaluable_accepting_an_invitation_gives_the_portal_user_the_mobile_number_the_invitation_carried","line":102},{"name":"a mobile already used by another account is skipped instead of breaking acceptance","eventName":"__pest_evaluable_a_mobile_already_used_by_another_account_is_skipped_instead_of_breaking_acceptance","line":118}],"name":"PortalInvitationTest","directories":["Portal"]},{"path":"tests\/Feature\/Portal\/PortalLoginTest.php","tests":[{"name":"portal login screen uses the Limosi auth design","eventName":"__pest_evaluable_portal_login_screen_uses_the_Limosi_auth_design","line":11},{"name":"a portal user can log in and reach the dashboard","eventName":"__pest_evaluable_a_portal_user_can_log_in_and_reach_the_dashboard","line":19},{"name":"wrong password fails without authenticating","eventName":"__pest_evaluable_wrong_password_fails_without_authenticating","line":36},{"name":"a suspended portal account is blocked by EnsurePortalAccess","eventName":"__pest_evaluable_a_suspended_portal_account_is_blocked_by_EnsurePortalAccess","line":52},{"name":"a suspended portal access is blocked by EnsurePortalAccess","eventName":"__pest_evaluable_a_suspended_portal_access_is_blocked_by_EnsurePortalAccess","line":65},{"name":"a staff account cannot log in through the portal login form","eventName":"__pest_evaluable_a_staff_account_cannot_log_in_through_the_portal_login_form","line":77}],"name":"PortalLoginTest","directories":["Portal"]},{"path":"tests\/Feature\/Portal\/PortalProjectScopingTest.php","tests":[{"name":"a project-scoped-only portal user cannot see a sibling project in the same company","eventName":"__pest_evaluable_a_project_scoped_only_portal_user_cannot_see_a_sibling_project_in_the_same_company","line":8},{"name":"full_company_access sees every project under the company","eventName":"__pest_evaluable_full__company__access_sees_every_project_under_the_company","line":31}],"name":"PortalProjectScopingTest","directories":["Portal"]},{"path":"tests\/Feature\/Portal\/SupportPeriodVisibilityTest.php","tests":[{"name":"draft, expired, and canceled support periods are not viewable","eventName":"__pest_evaluable_draft__expired__and_canceled_support_periods_are_not_viewable","line":19},{"name":"an active support period within its date range is viewable when contracts access is granted","eventName":"__pest_evaluable_an_active_support_period_within_its_date_range_is_viewable_when_contracts_access_is_granted","line":35},{"name":"an active support period is not viewable without can_view_contracts","eventName":"__pest_evaluable_an_active_support_period_is_not_viewable_without_can__view__contracts","line":49},{"name":"an active support period outside its date range is not viewable","eventName":"__pest_evaluable_an_active_support_period_outside_its_date_range_is_not_viewable","line":63}],"name":"SupportPeriodVisibilityTest","directories":["Portal"]},{"path":"tests\/Feature\/Quotations\/ExpireQuotationsCommandTest.php","tests":[{"name":"sent and viewed quotations past their valid_until date are marked expired","eventName":"__pest_evaluable_sent_and_viewed_quotations_past_their_valid__until_date_are_marked_expired","line":6},{"name":"quotations not yet past valid_until, and non-sent\/viewed statuses, are untouched","eventName":"__pest_evaluable_quotations_not_yet_past_valid__until__and_non_sent_viewed_statuses__are_untouched","line":28}],"name":"ExpireQuotationsCommandTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/NumberSequenceServiceTest.php","tests":[{"name":"sequential numbers increment and are formatted with the jalali year","eventName":"__pest_evaluable_sequential_numbers_increment_and_are_formatted_with_the_jalali_year","line":6},{"name":"sequences are isolated per workspace","eventName":"__pest_evaluable_sequences_are_isolated_per_workspace","line":21}],"name":"NumberSequenceServiceTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationCalculatorTest.php","tests":[{"name":"line item totals follow the spec formulas exactly","eventName":"__pest_evaluable_line_item_totals_follow_the_spec_formulas_exactly","line":26},{"name":"quotation totals aggregate correctly including other_cost_total and gross_profit","eventName":"__pest_evaluable_quotation_totals_aggregate_correctly_including_other__cost__total_and_gross__profit","line":59},{"name":"recalculateItem clamps discount to never exceed the raw total","eventName":"__pest_evaluable_recalculateItem_clamps_discount_to_never_exceed_the_raw_total","line":92}],"name":"QuotationCalculatorTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationCustomItemTest.php","tests":[{"name":"a custom item can be added to a quotation without a catalog item","eventName":"__pest_evaluable_a_custom_item_can_be_added_to_a_quotation_without_a_catalog_item","line":8},{"name":"a custom item cannot be added once the quotation is no longer a draft","eventName":"__pest_evaluable_a_custom_item_cannot_be_added_once_the_quotation_is_no_longer_a_draft","line":30},{"name":"the quotation show page adds a custom item through the full Livewire lifecycle","eventName":"__pest_evaluable_the_quotation_show_page_adds_a_custom_item_through_the_full_Livewire_lifecycle","line":42},{"name":"editing a custom item opens the edit modal pre-filled and saves the changes","eventName":"__pest_evaluable_editing_a_custom_item_opens_the_edit_modal_pre_filled_and_saves_the_changes","line":60},{"name":"editing a catalog-based item does not require re-selecting the catalog item","eventName":"__pest_evaluable_editing_a_catalog_based_item_does_not_require_re_selecting_the_catalog_item","line":88}],"name":"QuotationCustomItemTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationNoteTest.php","tests":[{"name":"the owner can create, update, and delete a quotation note","eventName":"__pest_evaluable_the_owner_can_create__update__and_delete_a_quotation_note","line":23},{"name":"a seller can manage quotation notes but finance cannot","eventName":"__pest_evaluable_a_seller_can_manage_quotation_notes_but_finance_cannot","line":40},{"name":"the notes page lists, creates, and deletes via the Livewire component","eventName":"__pest_evaluable_the_notes_page_lists__creates__and_deletes_via_the_Livewire_component","line":54},{"name":"selecting notes for a quotation attaches only those notes","eventName":"__pest_evaluable_selecting_notes_for_a_quotation_attaches_only_those_notes","line":73},{"name":"the quotation show page saves the selected notes through the full Livewire lifecycle","eventName":"__pest_evaluable_the_quotation_show_page_saves_the_selected_notes_through_the_full_Livewire_lifecycle","line":88},{"name":"the quotation PDF renders the selected notes and omits unselected ones","eventName":"__pest_evaluable_the_quotation_PDF_renders_the_selected_notes_and_omits_unselected_ones","line":104},{"name":"a seller can write a one-off custom note that is not editable\/deletable from the shared note list","eventName":"__pest_evaluable_a_seller_can_write_a_one_off_custom_note_that_is_not_editable_deletable_from_the_shared_note_list","line":130},{"name":"the quotation show page saves the custom note alongside selected notes","eventName":"__pest_evaluable_the_quotation_show_page_saves_the_custom_note_alongside_selected_notes","line":142},{"name":"the quotation PDF renders the custom note","eventName":"__pest_evaluable_the_quotation_PDF_renders_the_custom_note","line":157}],"name":"QuotationNoteTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationPdfGenerationTest.php","tests":[{"name":"the quotation PDF job produces a real, non-empty PDF file","eventName":"__pest_evaluable_the_quotation_PDF_job_produces_a_real__non_empty_PDF_file","line":18},{"name":"the quotation Excel job produces a formatted workbook for the quotation","eventName":"__pest_evaluable_the_quotation_Excel_job_produces_a_formatted_workbook_for_the_quotation","line":39},{"name":"the quotation show page can queue Excel generation","eventName":"__pest_evaluable_the_quotation_show_page_can_queue_Excel_generation","line":94}],"name":"QuotationPdfGenerationTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationPdfLayoutTest.php","tests":[{"name":"the items table declares columns in natural reading order so \u0631\u062f\u06cc\u0641 lands on the right","eventName":"__pest_evaluable_the_items_table_declares_columns_in_natural_reading_order_so_\u0631\u062f\u06cc\u0641_lands_on_the_right","line":19},{"name":"the seller info table declares columns in natural reading order so \u0646\u0627\u0645 lands on the right","eventName":"__pest_evaluable_the_seller_info_table_declares_columns_in_natural_reading_order_so_\u0646\u0627\u0645_lands_on_the_right","line":45},{"name":"the quotation PDF renders numeric content with Persian digits and Rial on the final total","eventName":"__pest_evaluable_the_quotation_PDF_renders_numeric_content_with_Persian_digits_and_Rial_on_the_final_total","line":65},{"name":"the notes block is set smaller than the body and separates each item","eventName":"__pest_evaluable_the_notes_block_is_set_smaller_than_the_body_and_separates_each_item","line":129},{"name":"the notes markup uses classes rather than inline styles so the spacing is editable in one place","eventName":"__pest_evaluable_the_notes_markup_uses_classes_rather_than_inline_styles_so_the_spacing_is_editable_in_one_place","line":148}],"name":"QuotationPdfLayoutTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationPdfLogoTest.php","tests":[{"name":"the quotation PDF embeds the workspace logo as base64 when one is set","eventName":"__pest_evaluable_the_quotation_PDF_embeds_the_workspace_logo_as_base64_when_one_is_set","line":14},{"name":"the quotation PDF renders without an img tag when no logo is set","eventName":"__pest_evaluable_the_quotation_PDF_renders_without_an_img_tag_when_no_logo_is_set","line":33}],"name":"QuotationPdfLogoTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationPublicViewTest.php","tests":[{"name":"opening the public link marks the quotation as viewed","eventName":"__pest_evaluable_opening_the_public_link_marks_the_quotation_as_viewed","line":44},{"name":"an invalid token shows not found without leaking data","eventName":"__pest_evaluable_an_invalid_token_shows_not_found_without_leaking_data","line":54},{"name":"the customer can approve a viewed quotation through the public link","eventName":"__pest_evaluable_the_customer_can_approve_a_viewed_quotation_through_the_public_link","line":59},{"name":"the customer can reject a viewed quotation through the public link","eventName":"__pest_evaluable_the_customer_can_reject_a_viewed_quotation_through_the_public_link","line":69}],"name":"QuotationPublicViewTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationShowRouteTest.php","tests":[{"name":"visiting a quotation show page directly does not throw NoWorkspaceContextException","eventName":"__pest_evaluable_visiting_a_quotation_show_page_directly_does_not_throw_NoWorkspaceContextException","line":15},{"name":"finance can view quotations created by another workspace member","eventName":"__pest_evaluable_finance_can_view_quotations_created_by_another_workspace_member","line":32}],"name":"QuotationShowRouteTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationStateMachineTest.php","tests":[{"name":"sending an empty draft quotation is rejected","eventName":"__pest_evaluable_sending_an_empty_draft_quotation_is_rejected","line":42},{"name":"sending generates a public token and transitions draft to sent","eventName":"__pest_evaluable_sending_generates_a_public_token_and_transitions_draft_to_sent","line":59},{"name":"sending a quotation twice is rejected (idempotent transition guard)","eventName":"__pest_evaluable_sending_a_quotation_twice_is_rejected__idempotent_transition_guard_","line":67},{"name":"a draft quotation cannot receive items removed or added once sent","eventName":"__pest_evaluable_a_draft_quotation_cannot_receive_items_removed_or_added_once_sent","line":74},{"name":"marking as viewed then approving follows the state graph","eventName":"__pest_evaluable_marking_as_viewed_then_approving_follows_the_state_graph","line":86},{"name":"approving directly from sent (skipping viewed) is allowed, for decisions relayed outside the tracked link","eventName":"__pest_evaluable_approving_directly_from_sent__skipping_viewed__is_allowed__for_decisions_relayed_outside_the_tracked_link","line":99},{"name":"rejecting directly from sent (skipping viewed) is allowed","eventName":"__pest_evaluable_rejecting_directly_from_sent__skipping_viewed__is_allowed","line":109},{"name":"rejecting a viewed quotation is audited","eventName":"__pest_evaluable_rejecting_a_viewed_quotation_is_audited","line":119},{"name":"creating a revision supersedes the original and copies items into a new draft","eventName":"__pest_evaluable_creating_a_revision_supersedes_the_original_and_copies_items_into_a_new_draft","line":130},{"name":"a revision cannot be created from a draft quotation","eventName":"__pest_evaluable_a_revision_cannot_be_created_from_a_draft_quotation","line":145},{"name":"a seller cannot approve their own quotation without the approve permission","eventName":"__pest_evaluable_a_seller_cannot_approve_their_own_quotation_without_the_approve_permission","line":162},{"name":"workspace A cannot see workspace B quotations","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_quotations","line":186}],"name":"QuotationStateMachineTest","directories":["Quotations"]},{"path":"tests\/Feature\/Quotations\/QuotationWithoutProjectTest.php","tests":[{"name":"a quotation can be created without choosing a project","eventName":"__pest_evaluable_a_quotation_can_be_created_without_choosing_a_project","line":20},{"name":"a quotation with no project offers every catalog item in the workspace","eventName":"__pest_evaluable_a_quotation_with_no_project_offers_every_catalog_item_in_the_workspace","line":41},{"name":"a project-less quotation can still be sent, approved and converted into a deal","eventName":"__pest_evaluable_a_project_less_quotation_can_still_be_sent__approved_and_converted_into_a_deal","line":54},{"name":"a project-less quotation is shown to full-access portal users of its company, not to project-scoped ones","eventName":"__pest_evaluable_a_project_less_quotation_is_shown_to_full_access_portal_users_of_its_company__not_to_project_scoped_ones","line":80},{"name":"a quotation issued to an organization with neither a customer nor a project still renders everywhere","eventName":"__pest_evaluable_a_quotation_issued_to_an_organization_with_neither_a_customer_nor_a_project_still_renders_everywhere","line":99}],"name":"QuotationWithoutProjectTest","directories":["Quotations"]},{"path":"tests\/Feature\/Reports\/ExportTest.php","tests":[{"name":"RequestExport creates a queued export row and dispatches the job","eventName":"__pest_evaluable_RequestExport_creates_a_queued_export_row_and_dispatches_the_job","line":16},{"name":"a seller without reports.export cannot request an export","eventName":"__pest_evaluable_a_seller_without_reports_export_cannot_request_an_export","line":29},{"name":"GenerateExportJob produces a downloadable file and notifies the requester","eventName":"__pest_evaluable_GenerateExportJob_produces_a_downloadable_file_and_notifies_the_requester","line":41},{"name":"a download request from a different user is forbidden","eventName":"__pest_evaluable_a_download_request_from_a_different_user_is_forbidden","line":68},{"name":"ExpireExportsCommand deletes the file and marks expired exports across all workspaces","eventName":"__pest_evaluable_ExpireExportsCommand_deletes_the_file_and_marks_expired_exports_across_all_workspaces","line":89}],"name":"ExportTest","directories":["Reports"]},{"path":"tests\/Feature\/Reports\/ReportQueryTest.php","tests":[{"name":"SalesReportQuery filters by seller and status and sums correctly","eventName":"__pest_evaluable_SalesReportQuery_filters_by_seller_and_status_and_sums_correctly","line":9},{"name":"CommissionReportQuery filters by beneficiary and status","eventName":"__pest_evaluable_CommissionReportQuery_filters_by_beneficiary_and_status","line":27}],"name":"ReportQueryTest","directories":["Reports"]},{"path":"tests\/Feature\/SMS\/KavenegarRepositoryTest.php","tests":[{"name":"an accepted message is posted to the dedicated line and reports the messageid","eventName":"__pest_evaluable_an_accepted_message_is_posted_to_the_dedicated_line_and_reports_the_messageid","line":40},{"name":"a single entry that comes back as a bare object still yields the messageid","eventName":"__pest_evaluable_a_single_entry_that_comes_back_as_a_bare_object_still_yields_the_messageid","line":58},{"name":"a per-message sender overrides the account line and is what gets sent","eventName":"__pest_evaluable_a_per_message_sender_overrides_the_account_line_and_is_what_gets_sent","line":67},{"name":"nothing is sent without a dedicated sender line, an api key or a text","eventName":"__pest_evaluable_nothing_is_sent_without_a_dedicated_sender_line__an_api_key_or_a_text","line":78},{"name":"localid is derived from the idempotency key: one numeric id per receptor, stable across retries","eventName":"__pest_evaluable_localid_is_derived_from_the_idempotency_key__one_numeric_id_per_receptor__stable_across_retries","line":94},{"name":"without an idempotency key no localid is sent","eventName":"__pest_evaluable_without_an_idempotency_key_no_localid_is_sent","line":110},{"name":"hide is only sent when configured","eventName":"__pest_evaluable_hide_is_only_sent_when_configured","line":118},{"name":"more than 200 receptors are split into separate calls","eventName":"__pest_evaluable_more_than_200_receptors_are_split_into_separate_calls","line":129},{"name":"a failure in a later chunk is not retryable, because the earlier chunk was already sent","eventName":"__pest_evaluable_a_failure_in_a_later_chunk_is_not_retryable__because_the_earlier_chunk_was_already_sent","line":140},{"name":"the documented credit code is reported as insufficient credit, not as a rejected message","eventName":"__pest_evaluable_the_documented_credit_code_is_reported_as_insufficient_credit__not_as_a_rejected_message","line":151},{"name":"a busy server or an over-limit IP is a retryable failure","eventName":"__pest_evaluable_a_busy_server_or_an_over_limit_IP_is_a_retryable_failure","line":162},{"name":"a refusal of the message itself is a rejection that should not be retried","eventName":"__pest_evaluable_a_refusal_of_the_message_itself_is_a_rejection_that_should_not_be_retried","line":172},{"name":"a body that is not kavenegar json is classified by its http status","eventName":"__pest_evaluable_a_body_that_is_not_kavenegar_json_is_classified_by_its_http_status","line":188},{"name":"an accepted call whose every message already failed is a rejection","eventName":"__pest_evaluable_an_accepted_call_whose_every_message_already_failed_is_a_rejection","line":202},{"name":"a read timeout is an unknown outcome, and the api key never leaks into the error","eventName":"__pest_evaluable_a_read_timeout_is_an_unknown_outcome__and_the_api_key_never_leaks_into_the_error","line":212},{"name":"a connection that never got through is a retryable failure","eventName":"__pest_evaluable_a_connection_that_never_got_through_is_a_retryable_failure","line":223},{"name":"the resolver builds the kavenegar repository and lists the workspace sender lines","eventName":"__pest_evaluable_the_resolver_builds_the_kavenegar_repository_and_lists_the_workspace_sender_lines","line":233},{"name":"a workspace event is sent as its full rendered text from the dedicated line","eventName":"__pest_evaluable_a_workspace_event_is_sent_as_its_full_rendered_text_from_the_dedicated_line","line":247},{"name":"the settings page saves a kavenegar line and no longer asks for patterns or a login","eventName":"__pest_evaluable_the_settings_page_saves_a_kavenegar_line_and_no_longer_asks_for_patterns_or_a_login","line":266},{"name":"turning kavenegar on without an api key or a dedicated line is refused","eventName":"__pest_evaluable_turning_kavenegar_on_without_an_api_key_or_a_dedicated_line_is_refused","line":290}],"name":"KavenegarRepositoryTest","directories":["SMS"]},{"path":"tests\/Feature\/SMS\/MelipayamakSendResultTest.php","tests":[{"name":"an accepted free-text message reports submitted with the panel's message id","eventName":"__pest_evaluable_an_accepted_free_text_message_reports_submitted_with_the_panel_s_message_id","line":31},{"name":"a plain numeric body is taken as the message id, and no id is still a success","eventName":"__pest_evaluable_a_plain_numeric_body_is_taken_as_the_message_id__and_no_id_is_still_a_success","line":42},{"name":"a per-message sender number overrides the account default, without one the default is used","eventName":"__pest_evaluable_a_per_message_sender_number_overrides_the_account_default__without_one_the_default_is_used","line":49},{"name":"a per-message sender lets a workspace with no default line still send free text, and is never sent as a pattern","eventName":"__pest_evaluable_a_per_message_sender_lets_a_workspace_with_no_default_line_still_send_free_text__and_is_never_sent_as_a_pattern","line":60},{"name":"without any sender number a free-text message fails clearly instead of being sent","eventName":"__pest_evaluable_without_any_sender_number_a_free_text_message_fails_clearly_instead_of_being_sent","line":70},{"name":"a refusal of one message is a rejection that should not be retried","eventName":"__pest_evaluable_a_refusal_of_one_message_is_a_rejection_that_should_not_be_retried","line":79},{"name":"the configured credit code is reported as insufficient credit, not as a rejected message","eventName":"__pest_evaluable_the_configured_credit_code_is_reported_as_insufficient_credit__not_as_a_rejected_message","line":90},{"name":"a configured transient code is retryable","eventName":"__pest_evaluable_a_configured_transient_code_is_retryable","line":100},{"name":"a server error or rate limit from the panel is a retryable failure","eventName":"__pest_evaluable_a_server_error_or_rate_limit_from_the_panel_is_a_retryable_failure","line":108},{"name":"a refused login is a rejection, not a retryable failure","eventName":"__pest_evaluable_a_refused_login_is_a_rejection__not_a_retryable_failure","line":118},{"name":"a read timeout is an unknown outcome and must not be marked retryable","eventName":"__pest_evaluable_a_read_timeout_is_an_unknown_outcome_and_must_not_be_marked_retryable","line":126},{"name":"a connection that never got through is a retryable failure","eventName":"__pest_evaluable_a_connection_that_never_got_through_is_a_retryable_failure","line":136},{"name":"the chain does not hand an unknown outcome to the next provider, which could send it twice","eventName":"__pest_evaluable_the_chain_does_not_hand_an_unknown_outcome_to_the_next_provider__which_could_send_it_twice","line":146},{"name":"the chain still falls back after a definite failure, and reports the last failure's classification when all fail","eventName":"__pest_evaluable_the_chain_still_falls_back_after_a_definite_failure__and_reports_the_last_failure_s_classification_when_all_fail","line":171},{"name":"the resolver can send through exactly one chosen provider and lists a workspace's sender lines","eventName":"__pest_evaluable_the_resolver_can_send_through_exactly_one_chosen_provider_and_lists_a_workspace_s_sender_lines","line":199},{"name":"nothing sends through a workspace that has sms disabled, even when a provider is named","eventName":"__pest_evaluable_nothing_sends_through_a_workspace_that_has_sms_disabled__even_when_a_provider_is_named","line":214}],"name":"MelipayamakSendResultTest","directories":["SMS"]},{"path":"tests\/Feature\/SMS\/SMSServiceTest.php","tests":[{"name":"sms service skips delivery when sms is disabled","eventName":"__pest_evaluable_sms_service_skips_delivery_when_sms_is_disabled","line":12},{"name":"melipayamak repository sends pattern sms with configured template parameters","eventName":"__pest_evaluable_melipayamak_repository_sends_pattern_sms_with_configured_template_parameters","line":25},{"name":"melipayamak repository sends the api key as the password alongside the existing username","eventName":"__pest_evaluable_melipayamak_repository_sends_the_api_key_as_the_password_alongside_the_existing_username","line":56},{"name":"a configured sender number always wins over a configured pattern id","eventName":"__pest_evaluable_a_configured_sender_number_always_wins_over_a_configured_pattern_id","line":87},{"name":"sms service sends token as code when the template expects a fixed-link pattern","eventName":"__pest_evaluable_sms_service_sends_token_as_code_when_the_template_expects_a_fixed_link_pattern","line":117},{"name":"workspace sms settings can select the active provider","eventName":"__pest_evaluable_workspace_sms_settings_can_select_the_active_provider","line":138},{"name":"chain sms repository falls back to the next provider","eventName":"__pest_evaluable_chain_sms_repository_falls_back_to_the_next_provider","line":161},{"name":"free-text sms for token-based links carries the complete url, not just the trailing code","eventName":"__pest_evaluable_free_text_sms_for_token_based_links_carries_the_complete_url__not_just_the_trailing_code","line":185}],"name":"SMSServiceTest","directories":["SMS"]},{"path":"tests\/Feature\/SMS\/SmsTriggersTest.php","tests":[{"name":"sending a quotation texts the customer","eventName":"__pest_evaluable_sending_a_quotation_texts_the_customer","line":43},{"name":"a customer approving a quotation texts the seller","eventName":"__pest_evaluable_a_customer_approving_a_quotation_texts_the_seller","line":57},{"name":"staff manually approving a quotation does not text the seller (they already know)","eventName":"__pest_evaluable_staff_manually_approving_a_quotation_does_not_text_the_seller__they_already_know_","line":72},{"name":"a customer rejecting a quotation texts the seller","eventName":"__pest_evaluable_a_customer_rejecting_a_quotation_texts_the_seller","line":85},{"name":"quotations:warn-expiring texts the customer once for a soon-to-expire sent quotation","eventName":"__pest_evaluable_quotations_warn_expiring_texts_the_customer_once_for_a_soon_to_expire_sent_quotation","line":100},{"name":"issuing an invoice texts every portal user with financial access","eventName":"__pest_evaluable_issuing_an_invoice_texts_every_portal_user_with_financial_access","line":118},{"name":"deals:warn-invoice-due texts financially-scoped portal users once for a soon-due invoice","eventName":"__pest_evaluable_deals_warn_invoice_due_texts_financially_scoped_portal_users_once_for_a_soon_due_invoice","line":141},{"name":"staff moving a ticket to waiting_for_customer texts the portal raiser","eventName":"__pest_evaluable_staff_moving_a_ticket_to_waiting__for__customer_texts_the_portal_raiser","line":163},{"name":"inviting a portal user with a mobile number texts them the invitation link","eventName":"__pest_evaluable_inviting_a_portal_user_with_a_mobile_number_texts_them_the_invitation_link","line":186},{"name":"activities:daily-digest texts a user with pending activities due today or overdue","eventName":"__pest_evaluable_activities_daily_digest_texts_a_user_with_pending_activities_due_today_or_overdue","line":205},{"name":"activities:remind-overdue texts the assignee of a still-pending activity overdue since a previous day","eventName":"__pest_evaluable_activities_remind_overdue_texts_the_assignee_of_a_still_pending_activity_overdue_since_a_previous_day","line":227},{"name":"activities:remind-overdue does not text for an activity only due earlier today","eventName":"__pest_evaluable_activities_remind_overdue_does_not_text_for_an_activity_only_due_earlier_today","line":249},{"name":"a staff reply on a ticket the customer raised texts that customer","eventName":"__pest_evaluable_a_staff_reply_on_a_ticket_the_customer_raised_texts_that_customer","line":271},{"name":"a staff reply on a ticket staff logged for the customer texts the company portal users who can see it","eventName":"__pest_evaluable_a_staff_reply_on_a_ticket_staff_logged_for_the_customer_texts_the_company_portal_users_who_can_see_it","line":294},{"name":"an internal note never texts the customer","eventName":"__pest_evaluable_an_internal_note_never_texts_the_customer","line":317}],"name":"SmsTriggersTest","directories":["SMS"]},{"path":"tests\/Feature\/Settings\/MembersInviteLinkTest.php","tests":[{"name":"a successful invite closes the invite form and opens the invite-link modal","eventName":"__pest_evaluable_a_successful_invite_closes_the_invite_form_and_opens_the_invite_link_modal","line":8},{"name":"the invite-link modal shows a link pointing at the invitations.accept route","eventName":"__pest_evaluable_the_invite_link_modal_shows_a_link_pointing_at_the_invitations_accept_route","line":27}],"name":"MembersInviteLinkTest","directories":["Settings"]},{"path":"tests\/Feature\/Settings\/SmsSettingsTest.php","tests":[{"name":"the page shows the exact sample text to register for each event pattern","eventName":"__pest_evaluable_the_page_shows_the_exact_sample_text_to_register_for_each_event_pattern","line":11},{"name":"the owner can connect their own sms panel and per-event patterns","eventName":"__pest_evaluable_the_owner_can_connect_their_own_sms_panel_and_per_event_patterns","line":24},{"name":"the owner can save an api key that is used as the password alongside the username","eventName":"__pest_evaluable_the_owner_can_save_an_api_key_that_is_used_as_the_password_alongside_the_username","line":48},{"name":"a workspace that never configured sms before can save without touching the provider dropdown","eventName":"__pest_evaluable_a_workspace_that_never_configured_sms_before_can_save_without_touching_the_provider_dropdown","line":82},{"name":"switching provider does not discard another provider previously saved settings","eventName":"__pest_evaluable_switching_provider_does_not_discard_another_provider_previously_saved_settings","line":103},{"name":"a seller cannot manage sms settings","eventName":"__pest_evaluable_a_seller_cannot_manage_sms_settings","line":135},{"name":"a saved workspace pattern is actually picked up when an sms is sent","eventName":"__pest_evaluable_a_saved_workspace_pattern_is_actually_picked_up_when_an_sms_is_sent","line":144},{"name":"extra sender lines are saved one per line, de-duplicated and without the default line","eventName":"__pest_evaluable_extra_sender_lines_are_saved_one_per_line__de_duplicated_and_without_the_default_line","line":171},{"name":"a sender line that is not a number is refused and nothing is saved","eventName":"__pest_evaluable_a_sender_line_that_is_not_a_number_is_refused_and_nothing_is_saved","line":197},{"name":"the owner can switch the automation module on and off, and each change is audited","eventName":"__pest_evaluable_the_owner_can_switch_the_automation_module_on_and_off__and_each_change_is_audited","line":212},{"name":"a member without permission to manage sms cannot switch automation on","eventName":"__pest_evaluable_a_member_without_permission_to_manage_sms_cannot_switch_automation_on","line":231}],"name":"SmsSettingsTest","directories":["Settings"]},{"path":"tests\/Feature\/Settlements\/SettlementTest.php","tests":[{"name":"creating a settlement settles the full remaining_amount of each selected entry","eventName":"__pest_evaluable_creating_a_settlement_settles_the_full_remaining__amount_of_each_selected_entry","line":52},{"name":"an entry not yet payable cannot be included in a settlement","eventName":"__pest_evaluable_an_entry_not_yet_payable_cannot_be_included_in_a_settlement","line":67},{"name":"confirming a settlement pays down the entry and marks it paid when fully settled","eventName":"__pest_evaluable_confirming_a_settlement_pays_down_the_entry_and_marks_it_paid_when_fully_settled","line":98},{"name":"canceling a confirmed settlement reverses the entry back to payable","eventName":"__pest_evaluable_canceling_a_confirmed_settlement_reverses_the_entry_back_to_payable","line":116},{"name":"a draft settlement can be canceled without touching its entries","eventName":"__pest_evaluable_a_draft_settlement_can_be_canceled_without_touching_its_entries","line":135},{"name":"a settlement cannot be confirmed twice","eventName":"__pest_evaluable_a_settlement_cannot_be_confirmed_twice","line":151}],"name":"SettlementTest","directories":["Settlements"]},{"path":"tests\/Feature\/Support\/SlaCalculatorTest.php","tests":[{"name":"a ticket raised within business hours gets a same-day deadline","eventName":"__pest_evaluable_a_ticket_raised_within_business_hours_gets_a_same_day_deadline","line":74},{"name":"a ticket raised before opening hours starts counting from the window start","eventName":"__pest_evaluable_a_ticket_raised_before_opening_hours_starts_counting_from_the_window_start","line":83},{"name":"a ticket raised after closing hours rolls over to the next working day","eventName":"__pest_evaluable_a_ticket_raised_after_closing_hours_rolls_over_to_the_next_working_day","line":92},{"name":"a response window that does not fit in one day spills into the next working day","eventName":"__pest_evaluable_a_response_window_that_does_not_fit_in_one_day_spills_into_the_next_working_day","line":103},{"name":"the weekend (Friday, off) is skipped entirely","eventName":"__pest_evaluable_the_weekend__Friday__off__is_skipped_entirely","line":115},{"name":"a specific holiday date is excluded when holiday_handling is exclude","eventName":"__pest_evaluable_a_specific_holiday_date_is_excluded_when_holiday__handling_is_exclude","line":127},{"name":"a holiday explicitly marked is_working_day overrides an otherwise-off Friday","eventName":"__pest_evaluable_a_holiday_explicitly_marked_is__working__day_overrides_an_otherwise_off_Friday","line":146},{"name":"holiday_handling include ignores a holiday closure and uses the normal weekday schedule","eventName":"__pest_evaluable_holiday__handling_include_ignores_a_holiday_closure_and_uses_the_normal_weekday_schedule","line":168},{"name":"sla_outside_hours_status running ignores business hours entirely","eventName":"__pest_evaluable_sla__outside__hours__status_running_ignores_business_hours_entirely","line":190},{"name":"a day-unit target always uses simple calendar days regardless of business hours","eventName":"__pest_evaluable_a_day_unit_target_always_uses_simple_calendar_days_regardless_of_business_hours","line":203},{"name":"a per-priority SLA rule overrides the period default response time","eventName":"__pest_evaluable_a_per_priority_SLA_rule_overrides_the_period_default_response_time","line":216},{"name":"resolution deadline is null when neither a rule nor the period defines one","eventName":"__pest_evaluable_resolution_deadline_is_null_when_neither_a_rule_nor_the_period_defines_one","line":239},{"name":"resolution deadline uses the period default when set","eventName":"__pest_evaluable_resolution_deadline_uses_the_period_default_when_set","line":248},{"name":"isBreached compares against now correctly","eventName":"__pest_evaluable_isBreached_compares_against_now_correctly","line":260},{"name":"falling back to service_days\/service_hours when no SupportSchedule rows exist at all","eventName":"__pest_evaluable_falling_back_to_service__days_service__hours_when_no_SupportSchedule_rows_exist_at_all","line":268},{"name":"a period with no working days at all throws instead of looping forever","eventName":"__pest_evaluable_a_period_with_no_working_days_at_all_throws_instead_of_looping_forever","line":287}],"name":"SlaCalculatorTest","directories":["Support"]},{"path":"tests\/Feature\/Support\/UpdateSupportPeriodStatusesCommandTest.php","tests":[{"name":"an upcoming period becomes active once its start date arrives","eventName":"__pest_evaluable_an_upcoming_period_becomes_active_once_its_start_date_arrives","line":7},{"name":"an active period expires once its end date has passed","eventName":"__pest_evaluable_an_active_period_expires_once_its_end_date_has_passed","line":24},{"name":"draft and suspended periods are never auto-transitioned","eventName":"__pest_evaluable_draft_and_suspended_periods_are_never_auto_transitioned","line":41}],"name":"UpdateSupportPeriodStatusesCommandTest","directories":["Support"]},{"path":"tests\/Feature\/Support\/WarnExpiringSupportPeriodsCommandTest.php","tests":[{"name":"an active period ending within 7 days notifies the workspace owner once","eventName":"__pest_evaluable_an_active_period_ending_within_7_days_notifies_the_workspace_owner_once","line":8},{"name":"a period ending far in the future is not warned","eventName":"__pest_evaluable_a_period_ending_far_in_the_future_is_not_warned","line":34}],"name":"WarnExpiringSupportPeriodsCommandTest","directories":["Support"]},{"path":"tests\/Feature\/Targets\/TargetImportTest.php","tests":[{"name":"an excel list is imported with normalised numbers, canonical provinces and per-row counts","eventName":"__pest_evaluable_an_excel_list_is_imported_with_normalised_numbers__canonical_provinces_and_per_row_counts","line":31},{"name":"a csv list imports the same way","eventName":"__pest_evaluable_a_csv_list_imports_the_same_way","line":68},{"name":"the whole list can be given a default priority and assigned to a member at import time","eventName":"__pest_evaluable_the_whole_list_can_be_given_a_default_priority_and_assigned_to_a_member_at_import_time","line":82},{"name":"a file without the mobile column is refused and nothing is created","eventName":"__pest_evaluable_a_file_without_the_mobile_column_is_refused_and_nothing_is_created","line":96},{"name":"an unsupported file type is refused","eventName":"__pest_evaluable_an_unsupported_file_type_is_refused","line":107},{"name":"a role without targets.create cannot import","eventName":"__pest_evaluable_a_role_without_targets_create_cannot_import","line":115},{"name":"a list is isolated to its workspace: the same number can exist in two workspaces","eventName":"__pest_evaluable_a_list_is_isolated_to_its_workspace__the_same_number_can_exist_in_two_workspaces","line":126},{"name":"uploading through the import modal reports the outcome and refuses a bad file with a message","eventName":"__pest_evaluable_uploading_through_the_import_modal_reports_the_outcome_and_refuses_a_bad_file_with_a_message","line":140},{"name":"an import with a last-name column stores the split name and a composed display name","eventName":"__pest_evaluable_an_import_with_a_last_name_column_stores_the_split_name_and_a_composed_display_name","line":166}],"name":"TargetImportTest","directories":["Targets"]},{"path":"tests\/Feature\/Targets\/TargetListTest.php","tests":[{"name":"the list filters by province, status, priority and city","eventName":"__pest_evaluable_the_list_filters_by_province__status__priority_and_city","line":24},{"name":"search finds a target by name, company or a number typed in persian digits","eventName":"__pest_evaluable_search_finds_a_target_by_name__company_or_a_number_typed_in_persian_digits","line":40},{"name":"the list filters by assignee, unassigned, list and follow-up state","eventName":"__pest_evaluable_the_list_filters_by_assignee__unassigned__list_and_follow_up_state","line":53},{"name":"sorting by priority puts urgent first","eventName":"__pest_evaluable_sorting_by_priority_puts_urgent_first","line":74},{"name":"quick row changes update status and priority, and assignment goes only to active workspace members","eventName":"__pest_evaluable_quick_row_changes_update_status_and_priority__and_assignment_goes_only_to_active_workspace_members","line":86},{"name":"bulk actions apply to the checked rows only","eventName":"__pest_evaluable_bulk_actions_apply_to_the_checked_rows_only","line":109},{"name":"selecting every match acts on all filtered targets across pages, not just the visible ones","eventName":"__pest_evaluable_selecting_every_match_acts_on_all_filtered_targets_across_pages__not_just_the_visible_ones","line":136},{"name":"select-page toggles every visible row","eventName":"__pest_evaluable_select_page_toggles_every_visible_row","line":152},{"name":"bulk delete removes the targets and their activities","eventName":"__pest_evaluable_bulk_delete_removes_the_targets_and_their_activities","line":163},{"name":"a manual target is created with a normalised number and a duplicate is refused","eventName":"__pest_evaluable_a_manual_target_is_created_with_a_normalised_number_and_a_duplicate_is_refused","line":174},{"name":"a seller only sees and can act on targets assigned to them","eventName":"__pest_evaluable_a_seller_only_sees_and_can_act_on_targets_assigned_to_them","line":197},{"name":"a viewer can see the whole list but change nothing","eventName":"__pest_evaluable_a_viewer_can_see_the_whole_list_but_change_nothing","line":227},{"name":"roles with no target permission cannot open the page and do not get the sidebar link","eventName":"__pest_evaluable_roles_with_no_target_permission_cannot_open_the_page_and_do_not_get_the_sidebar_link","line":243},{"name":"another workspace's targets are never listed","eventName":"__pest_evaluable_another_workspace_s_targets_are_never_listed","line":252},{"name":"the sample excel can be downloaded by someone who may import, and not by a viewer","eventName":"__pest_evaluable_the_sample_excel_can_be_downloaded_by_someone_who_may_import__and_not_by_a_viewer","line":265}],"name":"TargetListTest","directories":["Targets"]},{"path":"tests\/Feature\/Targets\/TargetShowTest.php","tests":[{"name":"the detail page renders the target and its activities","eventName":"__pest_evaluable_the_detail_page_renders_the_target_and_its_activities","line":14},{"name":"adding an activity records it, stamps the last activity and moves a new target to following","eventName":"__pest_evaluable_adding_an_activity_records_it__stamps_the_last_activity_and_moves_a_new_target_to_following","line":29},{"name":"adding an activity does not overwrite a status someone already set deliberately","eventName":"__pest_evaluable_adding_an_activity_does_not_overwrite_a_status_someone_already_set_deliberately","line":51},{"name":"an activity needs a valid type","eventName":"__pest_evaluable_an_activity_needs_a_valid_type","line":62},{"name":"an activity can be completed with a result, and deleted","eventName":"__pest_evaluable_an_activity_can_be_completed_with_a_result__and_deleted","line":71},{"name":"status, priority and assignee are changed from the detail page","eventName":"__pest_evaluable_status__priority_and_assignee_are_changed_from_the_detail_page","line":93},{"name":"all the information of a target can be edited, and a number used by another target is refused","eventName":"__pest_evaluable_all_the_information_of_a_target_can_be_edited__and_a_number_used_by_another_target_is_refused","line":111},{"name":"a seller cannot open a target that is not assigned to them, but can work their own","eventName":"__pest_evaluable_a_seller_cannot_open_a_target_that_is_not_assigned_to_them__but_can_work_their_own","line":144},{"name":"deleting a target from its page removes it and its activities","eventName":"__pest_evaluable_deleting_a_target_from_its_page_removes_it_and_its_activities","line":164},{"name":"first and last name can be set from the edit form and compose the display name when it is blank","eventName":"__pest_evaluable_first_and_last_name_can_be_set_from_the_edit_form_and_compose_the_display_name_when_it_is_blank","line":176}],"name":"TargetShowTest","directories":["Targets"]},{"path":"tests\/Feature\/Tasks\/TaskBoardTest.php","tests":[{"name":"the board groups tasks by status column","eventName":"__pest_evaluable_the_board_groups_tasks_by_status_column","line":9},{"name":"creating a task through the board form persists it with the chosen assignee and customer","eventName":"__pest_evaluable_creating_a_task_through_the_board_form_persists_it_with_the_chosen_assignee_and_customer","line":23},{"name":"editing a task through the board form updates it","eventName":"__pest_evaluable_editing_a_task_through_the_board_form_updates_it","line":50},{"name":"moveStatus moves a task between columns and sets completed_at for done","eventName":"__pest_evaluable_moveStatus_moves_a_task_between_columns_and_sets_completed__at_for_done","line":67},{"name":"deleting a task from the board removes it","eventName":"__pest_evaluable_deleting_a_task_from_the_board_removes_it","line":81}],"name":"TaskBoardTest","directories":["Tasks"]},{"path":"tests\/Feature\/Tasks\/TaskReminderTest.php","tests":[{"name":"the reminder text signs off with the sending workspace's own name, not a fixed company name","eventName":"__pest_evaluable_the_reminder_text_signs_off_with_the_sending_workspace_s_own_name__not_a_fixed_company_name","line":26},{"name":"a due recurring reminder texts the assignee and reschedules the next one","eventName":"__pest_evaluable_a_due_recurring_reminder_texts_the_assignee_and_reschedules_the_next_one","line":49},{"name":"a daily reminder reschedules to the exact same clock time the next day, not \"now\"","eventName":"__pest_evaluable_a_daily_reminder_reschedules_to_the_exact_same_clock_time_the_next_day__not__now_","line":69},{"name":"a due one-time reminder texts the assignee and disables itself afterward","eventName":"__pest_evaluable_a_due_one_time_reminder_texts_the_assignee_and_disables_itself_afterward","line":92},{"name":"a reminder not yet due is not sent","eventName":"__pest_evaluable_a_reminder_not_yet_due_is_not_sent","line":112},{"name":"a done task is never reminded even if next_reminder_at is due","eventName":"__pest_evaluable_a_done_task_is_never_reminded_even_if_next__reminder__at_is_due","line":128},{"name":"a disabled reminder is never sent even if next_reminder_at was left in the past","eventName":"__pest_evaluable_a_disabled_reminder_is_never_sent_even_if_next__reminder__at_was_left_in_the_past","line":146},{"name":"notify_team texts every active workspace member instead of just the assignee","eventName":"__pest_evaluable_notify__team_texts_every_active_workspace_member_instead_of_just_the_assignee","line":164}],"name":"TaskReminderTest","directories":["Tasks"]},{"path":"tests\/Feature\/Tasks\/TaskTest.php","tests":[{"name":"creating a task defaults assigned_to to the acting user when not given","eventName":"__pest_evaluable_creating_a_task_defaults_assigned__to_to_the_acting_user_when_not_given","line":11},{"name":"creating a task with a recurring 3-hour reminder schedules the first reminder 3 hours out","eventName":"__pest_evaluable_creating_a_task_with_a_recurring_3_hour_reminder_schedules_the_first_reminder_3_hours_out","line":22},{"name":"a daily reminder with a time-of-day in the future today fires later today","eventName":"__pest_evaluable_a_daily_reminder_with_a_time_of_day_in_the_future_today_fires_later_today","line":40},{"name":"a daily reminder with a time-of-day already past today rolls to tomorrow","eventName":"__pest_evaluable_a_daily_reminder_with_a_time_of_day_already_past_today_rolls_to_tomorrow","line":55},{"name":"creating a task with a one-time reminder schedules exactly that moment","eventName":"__pest_evaluable_creating_a_task_with_a_one_time_reminder_schedules_exactly_that_moment","line":70},{"name":"a task with the reminder disabled never gets a next_reminder_at","eventName":"__pest_evaluable_a_task_with_the_reminder_disabled_never_gets_a_next__reminder__at","line":85},{"name":"changing the recurring interval restarts the countdown from now","eventName":"__pest_evaluable_changing_the_recurring_interval_restarts_the_countdown_from_now","line":95},{"name":"turning the reminder off clears next_reminder_at but keeps the configured schedule","eventName":"__pest_evaluable_turning_the_reminder_off_clears_next__reminder__at_but_keeps_the_configured_schedule","line":113},{"name":"moving a task to done sets completed_at, moving it back clears it","eventName":"__pest_evaluable_moving_a_task_to_done_sets_completed__at__moving_it_back_clears_it","line":131},{"name":"deleting a task removes it","eventName":"__pest_evaluable_deleting_a_task_removes_it","line":144},{"name":"a viewer cannot create a task","eventName":"__pest_evaluable_a_viewer_cannot_create_a_task","line":155},{"name":"workspace A cannot see workspace B tasks","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_tasks","line":164}],"name":"TaskTest","directories":["Tasks"]},{"path":"tests\/Feature\/Tickets\/CheckTicketSlaBreachesCommandTest.php","tests":[{"name":"a ticket past its response deadline notifies the assigned agent once","eventName":"__pest_evaluable_a_ticket_past_its_response_deadline_notifies_the_assigned_agent_once","line":8},{"name":"a ticket within its SLA is not reported as breached","eventName":"__pest_evaluable_a_ticket_within_its_SLA_is_not_reported_as_breached","line":39},{"name":"a closed ticket past its old deadline is never reported","eventName":"__pest_evaluable_a_closed_ticket_past_its_old_deadline_is_never_reported","line":59}],"name":"CheckTicketSlaBreachesCommandTest","directories":["Tickets"]},{"path":"tests\/Feature\/Tickets\/TicketCreationTest.php","tests":[{"name":"staff can create a ticket for a project with an active support period","eventName":"__pest_evaluable_staff_can_create_a_ticket_for_a_project_with_an_active_support_period","line":17},{"name":"a portal user can create a ticket when the project has an active support period","eventName":"__pest_evaluable_a_portal_user_can_create_a_ticket_when_the_project_has_an_active_support_period","line":37},{"name":"portal ticket form lists projects connected through the company contract","eventName":"__pest_evaluable_portal_ticket_form_lists_projects_connected_through_the_company_contract","line":59},{"name":"a portal user cannot create a ticket without can_create_ticket permission","eventName":"__pest_evaluable_a_portal_user_cannot_create_a_ticket_without_can__create__ticket_permission","line":102},{"name":"a portal user cannot create a ticket when the support period has expired","eventName":"__pest_evaluable_a_portal_user_cannot_create_a_ticket_when_the_support_period_has_expired","line":121},{"name":"staff can create a ticket directly against a contract with no project","eventName":"__pest_evaluable_staff_can_create_a_ticket_directly_against_a_contract_with_no_project","line":149},{"name":"a portal user can create a ticket directly against a contract-level support period with no project","eventName":"__pest_evaluable_a_portal_user_can_create_a_ticket_directly_against_a_contract_level_support_period_with_no_project","line":169},{"name":"a portal user cannot create a contract-direct ticket without can_view_contracts","eventName":"__pest_evaluable_a_portal_user_cannot_create_a_contract_direct_ticket_without_can__view__contracts","line":191},{"name":"the portal ticket form offers a contract-only support arrangement as its own option, but not one already reachable through a project","eventName":"__pest_evaluable_the_portal_ticket_form_offers_a_contract_only_support_arrangement_as_its_own_option__but_not_one_already_reachable_through_a_project","line":209},{"name":"a portal user cannot create a ticket for a project outside their access","eventName":"__pest_evaluable_a_portal_user_cannot_create_a_ticket_for_a_project_outside_their_access","line":230}],"name":"TicketCreationTest","directories":["Tickets"]},{"path":"tests\/Feature\/Tickets\/TicketNotificationsTest.php","tests":[{"name":"creating a ticket from the portal notifies the workspace owner in-app","eventName":"__pest_evaluable_creating_a_ticket_from_the_portal_notifies_the_workspace_owner_in_app","line":12},{"name":"staff marking a ticket resolved emails the portal raiser","eventName":"__pest_evaluable_staff_marking_a_ticket_resolved_emails_the_portal_raiser","line":31},{"name":"a portal user confirming their own resolved ticket does not email themselves","eventName":"__pest_evaluable_a_portal_user_confirming_their_own_resolved_ticket_does_not_email_themselves","line":55}],"name":"TicketNotificationsTest","directories":["Tickets"]},{"path":"tests\/Feature\/Tickets\/TicketStatusAndSlaTest.php","tests":[{"name":"waiting_for_customer pauses the SLA clock and resuming shifts the deadline forward","eventName":"__pest_evaluable_waiting__for__customer_pauses_the_SLA_clock_and_resuming_shifts_the_deadline_forward","line":11},{"name":"a staff member can move a ticket through arbitrary statuses","eventName":"__pest_evaluable_a_staff_member_can_move_a_ticket_through_arbitrary_statuses","line":48},{"name":"a portal user can confirm a resolved ticket, closing it","eventName":"__pest_evaluable_a_portal_user_can_confirm_a_resolved_ticket__closing_it","line":75},{"name":"a portal user cannot arbitrarily change ticket status","eventName":"__pest_evaluable_a_portal_user_cannot_arbitrarily_change_ticket_status","line":100},{"name":"the first non-internal staff reply sets first_response_at exactly once","eventName":"__pest_evaluable_the_first_non_internal_staff_reply_sets_first__response__at_exactly_once","line":118}],"name":"TicketStatusAndSlaTest","directories":["Tickets"]},{"path":"tests\/Feature\/Tickets\/TicketVisibilityTest.php","tests":[{"name":"a portal user cannot view a ticket belonging to another company","eventName":"__pest_evaluable_a_portal_user_cannot_view_a_ticket_belonging_to_another_company","line":12},{"name":"without can_view_others_tickets, a portal user only sees their own tickets","eventName":"__pest_evaluable_without_can__view__others__tickets__a_portal_user_only_sees_their_own_tickets","line":34},{"name":"with can_view_others_tickets, a portal user sees every ticket on an accessible project","eventName":"__pest_evaluable_with_can__view__others__tickets__a_portal_user_sees_every_ticket_on_an_accessible_project","line":59},{"name":"a portal user can view a contract-direct ticket only when they can view contracts","eventName":"__pest_evaluable_a_portal_user_can_view_a_contract_direct_ticket_only_when_they_can_view_contracts","line":84},{"name":"internal notes never appear in the portal-facing message query","eventName":"__pest_evaluable_internal_notes_never_appear_in_the_portal_facing_message_query","line":111},{"name":"a portal user cannot post an internal note","eventName":"__pest_evaluable_a_portal_user_cannot_post_an_internal_note","line":143}],"name":"TicketVisibilityTest","directories":["Tickets"]},{"path":"tests\/Feature\/Workspace\/AcceptInvitationTest.php","tests":[{"name":"accepting a valid invitation creates an active membership","eventName":"__pest_evaluable_accepting_a_valid_invitation_creates_an_active_membership","line":29},{"name":"an expired invitation cannot be accepted","eventName":"__pest_evaluable_an_expired_invitation_cannot_be_accepted","line":44},{"name":"an invitation cannot be accepted by a different email address","eventName":"__pest_evaluable_an_invitation_cannot_be_accepted_by_a_different_email_address","line":54},{"name":"accepting the same invitation twice does not create a duplicate membership","eventName":"__pest_evaluable_accepting_the_same_invitation_twice_does_not_create_a_duplicate_membership","line":64}],"name":"AcceptInvitationTest","directories":["Workspace"]},{"path":"tests\/Feature\/Workspace\/CreateWorkspaceTest.php","tests":[{"name":"creating a workspace makes the creator its owner member","eventName":"__pest_evaluable_creating_a_workspace_makes_the_creator_its_owner_member","line":20},{"name":"the onboarding Livewire component creates a workspace and redirects to the dashboard","eventName":"__pest_evaluable_the_onboarding_Livewire_component_creates_a_workspace_and_redirects_to_the_dashboard","line":36},{"name":"the onboarding workspace page uses the limosi auth design","eventName":"__pest_evaluable_the_onboarding_workspace_page_uses_the_limosi_auth_design","line":48},{"name":"the onboarding workspace validation messages are localized","eventName":"__pest_evaluable_the_onboarding_workspace_validation_messages_are_localized","line":59},{"name":"a brand-new workspace has a default pipeline so a quotation can be converted to a deal","eventName":"__pest_evaluable_a_brand_new_workspace_has_a_default_pipeline_so_a_quotation_can_be_converted_to_a_deal","line":77}],"name":"CreateWorkspaceTest","directories":["Workspace"]},{"path":"tests\/Feature\/Workspace\/InviteMemberTest.php","tests":[{"name":"the owner can invite a new member and a notification is sent","eventName":"__pest_evaluable_the_owner_can_invite_a_new_member_and_a_notification_is_sent","line":10},{"name":"the invitation carries the plain token so the caller can build the accept link","eventName":"__pest_evaluable_the_invitation_carries_the_plain_token_so_the_caller_can_build_the_accept_link","line":26},{"name":"inviting with a mobile number texts the accept link","eventName":"__pest_evaluable_inviting_with_a_mobile_number_texts_the_accept_link","line":41},{"name":"inviting without a mobile number does not attempt to send an sms","eventName":"__pest_evaluable_inviting_without_a_mobile_number_does_not_attempt_to_send_an_sms","line":62},{"name":"a sales manager cannot invite members","eventName":"__pest_evaluable_a_sales_manager_cannot_invite_members","line":77},{"name":"a seller cannot invite members","eventName":"__pest_evaluable_a_seller_cannot_invite_members","line":89},{"name":"inviting an email that already belongs to the workspace is rejected","eventName":"__pest_evaluable_inviting_an_email_that_already_belongs_to_the_workspace_is_rejected","line":99}],"name":"InviteMemberTest","directories":["Workspace"]},{"path":"tests\/Feature\/Workspace\/RolePermissionsTest.php","tests":[{"name":"the owner role has every permission","eventName":"__pest_evaluable_the_owner_role_has_every_permission","line":8},{"name":"only the owner can invite, disable, or manage workspace settings","eventName":"__pest_evaluable_only_the_owner_can_invite__disable__or_manage_workspace_settings","line":14},{"name":"viewer only has view permissions","eventName":"__pest_evaluable_viewer_only_has_view_permissions","line":24},{"name":"finance can fully view quotations and deals","eventName":"__pest_evaluable_finance_can_fully_view_quotations_and_deals","line":32},{"name":"cms manager only has website.* permissions and nothing from any other domain","eventName":"__pest_evaluable_cms_manager_only_has_website___permissions_and_nothing_from_any_other_domain","line":38},{"name":"task viewer can only see the tasks permission and nothing else","eventName":"__pest_evaluable_task_viewer_can_only_see_the_tasks_permission_and_nothing_else","line":52},{"name":"sidebar only renders sections allowed for the current role","eventName":"__pest_evaluable_sidebar_only_renders_sections_allowed_for_the_current_role","line":58}],"name":"RolePermissionsTest","directories":["Workspace"]},{"path":"tests\/Feature\/Workspace\/TenantIsolationTest.php","tests":[{"name":"querying a tenant-scoped model without an active workspace context throws","eventName":"__pest_evaluable_querying_a_tenant_scoped_model_without_an_active_workspace_context_throws","line":8},{"name":"workspace A cannot see workspace B members through the global scope","eventName":"__pest_evaluable_workspace_A_cannot_see_workspace_B_members_through_the_global_scope","line":17},{"name":"creating a tenant-scoped record refuses a workspace_id that does not match the active context","eventName":"__pest_evaluable_creating_a_tenant_scoped_record_refuses_a_workspace__id_that_does_not_match_the_active_context","line":37},{"name":"route model binding scoped to the current workspace does not resolve another workspace member","eventName":"__pest_evaluable_route_model_binding_scoped_to_the_current_workspace_does_not_resolve_another_workspace_member","line":52}],"name":"TenantIsolationTest","directories":["Workspace"]},{"path":"tests\/Feature\/Workspace\/UpdateMemberRoleAndDisableTest.php","tests":[{"name":"the owner can change another member role and it is audited","eventName":"__pest_evaluable_the_owner_can_change_another_member_role_and_it_is_audited","line":9},{"name":"the owner role can never be changed or reassigned","eventName":"__pest_evaluable_the_owner_role_can_never_be_changed_or_reassigned","line":21},{"name":"a viewer cannot update member roles","eventName":"__pest_evaluable_a_viewer_cannot_update_member_roles","line":34},{"name":"the owner can disable a member and the owner cannot be disabled","eventName":"__pest_evaluable_the_owner_can_disable_a_member_and_the_owner_cannot_be_disabled","line":45},{"name":"finance cannot disable members","eventName":"__pest_evaluable_finance_cannot_disable_members","line":60}],"name":"UpdateMemberRoleAndDisableTest","directories":["Workspace"]}]}]__VSCODE_LARAVEL_END_OUTPUT__