diff --git a/.kube/app/entrypoint.sh b/.kube/app/entrypoint.sh index 4cce5cd7f..1c49319dc 100755 --- a/.kube/app/entrypoint.sh +++ b/.kube/app/entrypoint.sh @@ -18,6 +18,9 @@ php artisan deploy:local flock -n -E 0 /opt/data -c "php artisan deploy:global" # run exclusively on a single instance at once +# Run data migrations that aren't included in the DB migrations +flock -n -E 0 /opt/data -c "php artisan app:migrate-settings-data" # run exclusively on a single instance at once + # Generate the robots.txt and sitemap.xml files php artisan seo:generate diff --git a/README.md b/README.md index 8e41ffc7e..9835f7d22 100644 --- a/README.md +++ b/README.md @@ -179,20 +179,20 @@ of how some key tasks can be carried out using Herd: Herd supports debuging via XDebug. The article "[Activating XDebug on Visual Studio Code & Laravel Herd](https://thomashysselinckx.medium.com/activating-xdebug-on-visual-studio-code-laravel-herd-cfd0553d26e0)" can help if you are having trouble getting it setup with VS Code. -### Local development using Docker and Nix +### Local development using Docker and Nix -#### Setup Instructions +#### Setup Instructions -1. Install [Nix](https://nixos.org/download/) for your system. -2. Run `nix-shell`. -3. If you are wanting to run Docker, follow the steps for your platform: - - **Linux**: On Linux, there are added aliases `dstart` & `dstop` that will start and stop the Docker daemon, which runs using rootlesskit. - - When using rootless, ensure that it is set up and allowed to run on privileged ports. See: [Exposing Privileged Ports](https://github.com/rootless-containers/rootlesskit/blob/master/docs/port.md#exposing-privileged-ports). - - You will also want to change the socket path with the following command: +1. Install [Nix](https://nixos.org/download/) for your system. +2. Run `nix-shell`. +3. If you are wanting to run Docker, follow the steps for your platform: + - **Linux**: On Linux, there are added aliases `dstart` & `dstop` that will start and stop the Docker daemon, which runs using rootlesskit. + - When using rootless, ensure that it is set up and allowed to run on privileged ports. See: [Exposing Privileged Ports](https://github.com/rootless-containers/rootlesskit/blob/master/docs/port.md#exposing-privileged-ports). + - You will also want to change the socket path with the following command: ```sh export DOCKER_HOST=unix:///run/user/1000/docker.sock - ``` - - **Other Systems**: You will need to have Docker installed and running. + ``` + - **Other Systems**: You will need to have Docker installed and running. #### Entering Development Environment @@ -343,34 +343,34 @@ kflushall # Run kflush dev, staging, and prod environments | `-p` | Prompts for confirmation before executing | | `| grep ...` | Filters output based on pattern matching | -#### Environment Setup +#### Environment Setup -If the `.env` file does not exist, the script automatically generates it using `.env.local.template` and random secrets: -- `CIPHERSWEET_KEY` (32-byte hex string) -- `DB_PASSWORD` (16-byte hex string) -- `DB_ROOT_PASSWORD` (24-byte hex string) -- `REDIS_PASSWORD` (20-byte hex string) -- `APP_KEY` (generated using `php artisan key:generate`) -- `WWWUSER` (set to current user ID) +If the `.env` file does not exist, the script automatically generates it using `.env.local.template` and random secrets: +- `CIPHERSWEET_KEY` (32-byte hex string) +- `DB_PASSWORD` (16-byte hex string) +- `DB_ROOT_PASSWORD` (24-byte hex string) +- `REDIS_PASSWORD` (20-byte hex string) +- `APP_KEY` (generated using `php artisan key:generate`) +- `WWWUSER` (set to current user ID) -Ensure `.env.local.template` is available before running the script. +Ensure `.env.local.template` is available before running the script. -#### Rootless Docker Support +#### Rootless Docker Support -For users running `dockerd-rootless`, the script provides: -- Aliases: +For users running `dockerd-rootless`, the script provides: +- Aliases: ```sh alias dstart="dockerd-rootless&" alias dstop="pkill dockerd" - ``` -- Instructions to set the correct Docker socket: + ``` +- Instructions to set the correct Docker socket: ```sh export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock - ``` -- To allow privileged ports, run: + ``` +- To allow privileged ports, run: ```sh echo 1 | sudo tee /proc/sys/net/ipv4/ip_unprivileged_port_start - ``` + ``` #### Troubleshooting @@ -469,6 +469,13 @@ Runs other console commands in order and should be commands that are only run on Runs other console commands in order and should be commands that should be run on each deploying container. +### app:migrate-settings-data + +#### Purpose + +Runs data migrations that cannot be included in the DB migrations. Should only be run oce across a multiple deploying +container. + ### notifications:remove:old #### Purpose diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 871b74f06..fd0e93c7c 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -41,6 +41,10 @@ public function create(array $input): User } } + if ($input['context'] === UserContext::Individual->value) { + $input['notification_settings'] = ['engagements' => '1']; + } + Validator::make( $input, [ @@ -63,6 +67,7 @@ public function create(array $input): User 'locale' => ['required', Rule::in(config('locales.supported'))], 'accepted_privacy_policy' => 'accepted', 'accepted_terms_of_service' => 'accepted', + 'notification_settings.engagements' => 'nullable|boolean', ], [ 'accepted_privacy_policy.accepted' => __('You must agree to the privacy policy.'), @@ -88,6 +93,7 @@ public function create(array $input): User 'extra_attributes' => $input['extra_attributes'] ?? null, 'accepted_privacy_policy_at' => now(), 'accepted_terms_of_service_at' => now(), + 'notification_settings' => $input['notification_settings'] ?? null, ]); } } diff --git a/app/Console/Commands/MigrateSettingsData.php b/app/Console/Commands/MigrateSettingsData.php new file mode 100644 index 000000000..3094d6948 --- /dev/null +++ b/app/Console/Commands/MigrateSettingsData.php @@ -0,0 +1,148 @@ + [ + 'version' => '1.7.0', + 'handler' => 'enableEngagementNotificationsMigration', + 'description' => 'Replaces older format of notifications_settings with only ["engagements" => "1"]. Setting the engagement notifications on be default. If the notifications_settings contains a valid engagements setting, then no changes are made.', + ], + ]; + + /** + * Execute the console command. + */ + public function handle() + { + $verbose = $this->output->isVerbose(); + + if ($this->options()['list']) { + + $this->listMigrations($this->migrations); + + return 0; + } + + if ($this->options()['migration']) { + try { + $migration = $this->migrations[$this->options()['migration']]; + } catch (Exception $e) { + $this->fail('Could not find migration: '.$this->options()['migration']); + } + + if (isset($migration)) { + $handler = $migration['handler']; + $this->$handler(); + + return 0; + } + } + + $this->runMigrations($this->migrations, $this->options()['from'], $verbose); + } + + public function listMigrations($migrations) + { + $definitions = ''; + + foreach ($migrations as $name => $migration) { + $version = $migration['version']; + $description = $migration['description']; + $definitions .= "
$name
Version added: $version
$description
"; + + $this->line("$name (Version added: $version)"); + $this->info("$description"); + $this->newLine(); + } + } + + public function runMigrations($migrations, $from = '1.6.0', $verbose = false) + { + $from = str_starts_with($from, 'v') || str_starts_with($from, 'V') ? substr($from, 1) : $from; + $migrationRunCount = 0; + + foreach ($migrations as $name => $migration) { + if (version_compare($from, $migration['version'], '<')) { + if ($verbose) { + $this->line("Run migration - $name"); + } + $handler = $migration['handler']; + $this->$handler($verbose); + $migrationRunCount++; + } elseif ($verbose) { + $this->comment("Skipped migration - $name"); + } + + if ($verbose) { + $this->newLine(); + } + } + + $this->line('Completed '.$migrationRunCount.''); + $this->line('Skipped '.(count($migrations) - $migrationRunCount).''); + } + + // Migrations + + public function enableEngagementNotificationsMigration($verbose = false) + { + $updatedNotificationSettings = ['engagements' => '1']; + + if ($verbose) { + $this->info(' - Migrating engagement notifications for users'); + } + + $users = User::where('context', 'individual')->whereNull('notification_settings->engagements') + ->get(); + + $users->each(function ($user) use ($updatedNotificationSettings) { + // @phpstan-ignore assign.propertyType + $user->notification_settings = $updatedNotificationSettings; + $user->save(); + }); + + if ($verbose) { + $this->info(' - Migrated '.$users->count().' users'); + $this->info(' - Migrating engagement notification settings for Organizations'); + } + + $orgs = Organization::whereNull('notification_settings->engagements') + ->get(); + + $orgs->each(function ($organization) use ($updatedNotificationSettings) { + // @phpstan-ignore assign.propertyType + $organization->notification_settings = $updatedNotificationSettings; + $organization->save(); + }); + + if ($verbose) { + $this->info(' - Migrated '.$orgs->count().' Organizations'); + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 25b2940a6..e375bd4f3 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -23,7 +23,7 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command('app:refresh-dev') // use custom command to make sure that te commands are chained + $schedule->command('app:refresh-dev') // use custom command to make sure that the commands are chained ->daily() // Run daily at midnight ->environments(['dev']) // only run for APP_ENV tagged dev ->timezone('America/Los_Angeles') // Run as PST timezone diff --git a/app/Enums/YesNo.php b/app/Enums/YesNo.php new file mode 100644 index 000000000..cbf81c68f --- /dev/null +++ b/app/Enums/YesNo.php @@ -0,0 +1,17 @@ + __('Yes'), + '0' => __('No'), + ]; + } +} diff --git a/app/Http/Controllers/EngagementController.php b/app/Http/Controllers/EngagementController.php index bba305fba..0c7e4bb4c 100644 --- a/app/Http/Controllers/EngagementController.php +++ b/app/Http/Controllers/EngagementController.php @@ -31,6 +31,7 @@ use App\Models\Project; use App\Models\User; use App\Notifications\AccessNeedsFacilitationRequested; +use App\Notifications\EngagementAdded; use App\Notifications\JoinedEngagement; use App\Notifications\LeftEngagement; use App\Notifications\OrganizationAddedToEngagement; @@ -47,6 +48,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Notification as FacadesNotification; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Notification; @@ -422,6 +424,21 @@ public function update(UpdateEngagementRequest $request, Engagement $engagement) if ($engagement->fresh()->isPublishable()) { $engagement->update(['published_at' => now()]); flash(__('Your engagement has been published.'), 'success|'.__('Your engagement has been published.', [], 'en')); + + if ($engagement->recruitment === EngagementRecruitment::OpenCall->value) { + $users = User::where('context', 'individual')->withNotificationSettings('engagements', '1')->get(); + FacadesNotification::send($users, new EngagementAdded($engagement)); + } + + $projectable = $engagement->project->projectable; + + $otherOrgs = Organization::when($projectable instanceof Organization, fn ($query) => $query->whereNot(fn ($query) => $query->where('id', $projectable->id))) + ->withNotificationSettings('engagements', '1') + ->get(); + + if ($otherOrgs->count()) { + FacadesNotification::send($otherOrgs, new EngagementAdded($engagement)); + } } } else { flash(__('Your engagement has been updated.'), 'success|'.__('Your engagement has been updated.', [], 'en')); diff --git a/app/Http/Controllers/IndividualController.php b/app/Http/Controllers/IndividualController.php index 4bf619cd3..3a9e72b46 100644 --- a/app/Http/Controllers/IndividualController.php +++ b/app/Http/Controllers/IndividualController.php @@ -10,6 +10,7 @@ use App\Enums\IndividualRole; use App\Enums\MeetingType; use App\Enums\ProvinceOrTerritory; +use App\Enums\YesNo; use App\Http\Requests\DestroyIndividualRequest; use App\Http\Requests\SaveIndividualRolesRequest; use App\Http\Requests\UpdateIndividualCommunicationAndConsultationPreferencesRequest; @@ -137,10 +138,7 @@ public function edit(Individual $individual): View 'indigenousIdentities' => Options::forModels(Identity::query()->whereJsonContains('clusters', IdentityCluster::Indigenous))->toArray(), 'languages' => Options::forArray(get_available_languages(true))->nullable(__('Choose a language…'))->toArray(), 'livedExperiences' => Options::forModels(Identity::query()->whereJsonContains('clusters', IdentityCluster::LivedExperience)->withoutGlobalScope(ReachableIdentityScope::class))->toArray(), - 'yesNoOptions' => Options::forArray([ - '1' => __('Yes'), - '0' => __('No'), - ])->toArray(), + 'yesNoOptions' => Options::forEnum(YesNo::class)->toArray(), 'communityConnectorHasLivedExperience' => Options::forEnum(CommunityConnectorHasLivedExperience::class)->toArray(), 'contactPeople' => Options::forEnum(ContactPerson::class)->toArray(), 'meetingTypes' => Options::forEnum(MeetingType::class)->toArray(), diff --git a/app/Http/Controllers/OrganizationController.php b/app/Http/Controllers/OrganizationController.php index 92099fb08..8d6280590 100644 --- a/app/Http/Controllers/OrganizationController.php +++ b/app/Http/Controllers/OrganizationController.php @@ -10,6 +10,7 @@ use App\Enums\ProvinceOrTerritory; use App\Enums\StaffHaveLivedExperience; use App\Enums\TeamRole; +use App\Enums\YesNo; use App\Http\Requests\DestroyOrganizationRequest; use App\Http\Requests\SaveOrganizationRolesRequest; use App\Http\Requests\StoreOrganizationLanguagesRequest; @@ -82,6 +83,8 @@ public function store(StoreOrganizationRequest $request): RedirectResponse $data['languages'] = get_supported_locales(false); + $data['notification_settings'] = ['engagements' => '1']; + $organization = Organization::create($data); session()->forget('type'); @@ -183,10 +186,7 @@ public function edit(Organization $organization): View 'indigenousIdentities' => Options::forModels(Identity::query()->whereJsonContains('clusters', IdentityCluster::Indigenous))->toArray(), 'languages' => Options::forArray(get_available_languages(true))->nullable(__('Choose a language…'))->toArray(), 'livedExperiences' => Options::forModels(Identity::query()->whereJsonContains('clusters', IdentityCluster::LivedExperience)->withoutGlobalScope(ReachableIdentityScope::class))->toArray(), - 'yesNoOptions' => Options::forArray([ - '1' => __('Yes'), - '0' => __('No'), - ])->toArray(), + 'yesNoOptions' => Options::forEnum(YesNo::class)->toArray(), 'staffHaveLivedExperience' => Options::forEnum(StaffHaveLivedExperience::class)->toArray(), ]); } diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index e31ea6a8b..d15a6cc6c 100644 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -11,6 +11,7 @@ use App\Enums\ProvinceOrTerritory; use App\Enums\TeamRole; use App\Enums\Theme; +use App\Enums\YesNo; use App\Http\Requests\UpdateAccessNeedsRequest; use App\Http\Requests\UpdateAreasOfInterestRequest; use App\Http\Requests\UpdateCommunicationAndConsultationPreferencesRequest; @@ -357,6 +358,7 @@ public function editNotificationPreferences(): View 'organizationNotificationChannels' => Options::forEnum(OrganizationNotificationChannel::class)->toArray(), 'projectNotificationTypes' => Options::forArray($projectNotificationTypes)->toArray(), 'engagementNotificationTypes' => Options::forArray($engagementNotificationTypes)->toArray(), + 'yesNoOptions' => Options::forEnum(YesNo::class)->toArray(), ]); } diff --git a/app/Http/Requests/UpdateNotificationPreferencesRequest.php b/app/Http/Requests/UpdateNotificationPreferencesRequest.php index bcfa0d2a3..fa86e5830 100644 --- a/app/Http/Requests/UpdateNotificationPreferencesRequest.php +++ b/app/Http/Requests/UpdateNotificationPreferencesRequest.php @@ -2,12 +2,7 @@ namespace App\Http\Requests; -use App\Enums\NotificationChannel; -use App\Enums\NotificationMethod; -use App\Enums\OrganizationNotificationChannel; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; -use Illuminate\Validation\Rules\Enum; class UpdateNotificationPreferencesRequest extends FormRequest { @@ -21,118 +16,14 @@ public function rules(): array $user = request()->user(); return [ - 'preferred_notification_method' => [ - Rule::requiredIf(in_array($user->context, ['individual', 'organization'])), - new Enum(NotificationMethod::class), - ], - 'notification_settings.consultants.channels' => [ - 'nullable', - 'array', - ], - 'notification_settings.consultants.channels.*' => [ - new Enum(NotificationChannel::class), - ], - 'notification_settings.connectors.channels' => [ - 'nullable', - 'array', - ], - 'notification_settings.connectors.channels.*' => [ - new Enum(NotificationChannel::class), - ], - 'notification_settings.reports.channels' => [ - 'nullable', - 'array', - ], - 'notification_settings.reports.channels.*' => [ - new Enum(NotificationChannel::class), - ], - 'notification_settings.projects.channels' => [ - 'nullable', - 'array', - ], - 'notification_settings.projects.channels.*' => [ - $user->context === 'individual' ? new Enum(NotificationChannel::class) : new Enum(OrganizationNotificationChannel::class), - ], - 'notification_settings.projects.creators' => [ - 'nullable', - 'exclude_without:notification_settings.projects.channels', - 'required_with:notification_settings.projects.channels', - 'array', - ], - 'notification_settings.projects.creators.*' => 'in:organizations,regulated-organizations', - 'notification_settings.projects.types' => [ - 'nullable', - 'exclude_without:notification_settings.projects.channels', - 'required_with:notification_settings.projects.channels', - 'array', - ], - 'notification_settings.projects.types.*' => $user->context === 'individual' ? 'in:lived-experience,of-interest' : 'in:constituents', - 'notification_settings.projects.engagements' => [ - 'nullable', - 'exclude_without:notification_settings.projects.channels', - 'required_with:notification_settings.projects.channels', - 'array', - ], - 'notification_settings.projects.engagements.*' => $user->context === 'individual' ? 'in:lived-experience,of-interest' : 'in:constituents', - 'notification_settings.updates.channels' => [ - 'nullable', - 'array', - ], - 'notification_settings.updates.channels.*' => [ - new Enum(NotificationChannel::class), - ], + 'notification_settings.engagements' => 'required|boolean', ]; } public function attributes(): array { return [ - 'preferred_notification_method' => __('Preferred notification method'), - 'notification_settings.consultants.channels' => __('Accessibility Consultant notification setting'), - 'notification_settings.consultants.channels.*' => __('Accessibility Consultant notification setting'), - 'notification_settings.connectors.channels' => __('Community Connector notification setting'), - 'notification_settings.connectors.channels.*' => __('Community Connector notification setting'), - 'notification_settings.reports.channels' => __('report notification setting'), - 'notification_settings.reports.channels.*' => __('report notification setting'), - 'notification_settings.projects.channels' => __('projects notification setting'), - 'notification_settings.projects.channels.*' => __('projects notification setting'), - 'notification_settings.projects.creators' => __('project created by organization type notification setting'), - 'notification_settings.projects.creators.*' => __('project created by organization type notification setting'), - 'notification_settings.projects.types' => __('project type notification setting'), - 'notification_settings.projects.types.*' => __('project type notification setting'), - 'notification_settings.projects.engagements' => __('project engagement type notification setting'), - 'notification_settings.projects.engagements.*' => __('project engagement type notification setting'), - 'notification_settings.updates.channels' => __('review and updates notification settings'), - 'notification_settings.updates.channels.*' => __('review and updates notification settings'), - ]; - } - - public function prepareForValidation() - { - $fallbacks = [ - 'notification_settings.consultants.channels' => [], - 'notification_settings.connectors.channels' => [], - 'notification_settings.reports.channels' => [], - 'notification_settings.projects.channels' => [], - 'notification_settings.projects.creators' => [], - 'notification_settings.projects.types' => [], - 'notification_settings.projects.engagements' => [], - 'notification_settings.updates.channels' => [], - ]; - - // Prepare input for validation - $this->mergeIfMissing($fallbacks); - - // Prepare old input in case of validation failure - request()->mergeIfMissing($fallbacks); - } - - public function messages(): array - { - return [ - 'notification_settings.projects.creators.required_with' => __('You must choose at least one type of organization.'), - 'notification_settings.projects.types.required_with' => __('You must choose at least one type of project.'), - 'notification_settings.projects.engagements.required_with' => __('You must choose at least one type of engagement.'), + 'notification_settings.engagements' => __('engagements notification setting'), ]; } } diff --git a/app/Models/Organization.php b/app/Models/Organization.php index 77759afa2..407e3066b 100644 --- a/app/Models/Organization.php +++ b/app/Models/Organization.php @@ -570,8 +570,13 @@ public function displayRoles(): Attribute ); } - public function scopeWithExtraAttributes(): Builder + public function scopeWithExtraAttributes(...$args): Builder { return $this->extra_attributes->modelScope(); } + + public function scopeWithNotificationSettings(...$args): Builder + { + return $this->notification_settings->modelScope(); + } } diff --git a/app/Models/User.php b/app/Models/User.php index c4e8f1c07..3775f9374 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -134,12 +134,12 @@ public function routeNotificationForVonage(Notification $notification): string }; } - public function scopeWithExtraAttributes(): Builder + public function scopeWithExtraAttributes(...$args): Builder { return $this->extra_attributes->modelScope(); } - public function scopeWithNotificationSettings(): Builder + public function scopeWithNotificationSettings(...$args): Builder { return $this->notification_settings->modelScope(); } diff --git a/app/Notifications/EngagementAdded.php b/app/Notifications/EngagementAdded.php new file mode 100644 index 000000000..49eb4c1f5 --- /dev/null +++ b/app/Notifications/EngagementAdded.php @@ -0,0 +1,39 @@ +engagement = $engagement; + $this->projectable = $this->engagement->project->projectable; + } + + public function toMail(): MailMessage + { + return (new MailMessage) + ->subject(__('New Engagement from :projectable', ['projectable' => $this->projectable->getTranslation('name', locale())])) + ->markdown( + 'mail.engagement-added', + [ + 'engagement' => $this->engagement, + 'projectable' => $this->projectable, + ] + ); + } + + public function toArray(): array + { + return [ + 'engagement_id' => $this->engagement->id, + ]; + } +} diff --git a/app/View/Components/Notification/EngagementAdded.php b/app/View/Components/Notification/EngagementAdded.php new file mode 100644 index 000000000..779eee47e --- /dev/null +++ b/app/View/Components/Notification/EngagementAdded.php @@ -0,0 +1,44 @@ +engagement = Engagement::find($notification->data['engagement_id']); + /** @var Organization|RegulatedOrganization */ + $projectable = $this->engagement->project->projectable; + $this->title = __('New engagement added'); + $this->body = safe_markdown('A new engagement has been uploaded: [:engagement](:engagement_url) by [:projectable](:projectable_url)', [ + 'engagement' => $this->engagement->getTranslation('name', locale()), + 'engagement_url' => localized_route('engagements.show', $this->engagement), + 'projectable' => $projectable->getTranslation('name', locale()), + 'projectable_url' => localized_route($projectable->getRoutePrefix().'.show', $projectable), + ]); + $this->interpretation = __('New engagement added', [], 'en'); + + parent::__construct($notification); + } + + public function render(): View + { + return view('components.notification.engagement-added', [ + 'notification' => $this->notification, + 'read' => ! is_null($this->notification->read_at), + 'title' => $this->title, + 'body' => $this->body, + 'engagement' => $this->engagement, + 'interpretation' => $this->interpretation, + ]); + } +} diff --git a/database/factories/EngagementFactory.php b/database/factories/EngagementFactory.php index 0c4497d25..8aabd5e0a 100644 --- a/database/factories/EngagementFactory.php +++ b/database/factories/EngagementFactory.php @@ -2,6 +2,8 @@ namespace Database\Factories; +use App\Enums\EngagementFormat; +use App\Enums\EngagementRecruitment; use App\Models\Project; use Illuminate\Database\Eloquent\Factories\Factory; @@ -16,8 +18,8 @@ public function definition(): array 'languages' => config('locales.supported'), 'who' => 'individuals', 'description' => ['en' => 'About this engagement'], - 'format' => 'workshop', - 'recruitment' => 'open-call', + 'format' => EngagementFormat::Workshop->value, + 'recruitment' => EngagementRecruitment::OpenCall->value, 'ideal_participants' => 25, 'minimum_participants' => 15, 'paid' => true, diff --git a/database/factories/OrganizationFactory.php b/database/factories/OrganizationFactory.php index 96135c348..d2a9896fa 100644 --- a/database/factories/OrganizationFactory.php +++ b/database/factories/OrganizationFactory.php @@ -20,6 +20,7 @@ public function definition(): array 'contact_person_email' => $this->faker->email(), 'oriented_at' => now(), 'validated_at' => now(), + 'notification_settings' => ['engagements' => '1'], ]; } } diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 96a9b0551..2ad335a98 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -38,6 +38,7 @@ public function definition(): array 'accepted_privacy_policy_at' => now(), 'accepted_terms_of_service_at' => now(), 'oriented_at' => now(), + 'notification_settings' => fn (array $attributes) => $attributes['context'] === UserContext::Individual->value ? ['engagements' => '1'] : [], ]; } } diff --git a/database/seeders/DevSeeder.php b/database/seeders/DevSeeder.php index caae6e63a..fed7de2c4 100644 --- a/database/seeders/DevSeeder.php +++ b/database/seeders/DevSeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use App\Enums\UserContext; use App\Models\User; use Illuminate\Database\Seeder; @@ -14,7 +15,7 @@ public function run(): void 'name' => 'Administrator', 'email' => 'info+admin@accessibilityexchange.ca', 'email_verified_at' => now(), - 'context' => 'administrator', + 'context' => UserContext::Administrator->value, ]); $this->call([ @@ -36,7 +37,7 @@ public function run(): void 'name' => 'Regulated Organization User', 'email' => 'info+regulated-organization@accessibilityexchange.ca', 'email_verified_at' => now(), - 'context' => 'regulated-organization', + 'context' => UserContext::RegulatedOrganization->value, ]); $organizationUser = User::factory() @@ -44,7 +45,7 @@ public function run(): void 'name' => 'Community Organization User', 'email' => 'info+organization@accessibilityexchange.ca', 'email_verified_at' => now(), - 'context' => 'organization', + 'context' => UserContext::Organization->value, ]); $trainingUser = User::factory() @@ -52,7 +53,7 @@ public function run(): void 'name' => 'Training User', 'email' => 'info+training@accessibilityexchange.ca', 'email_verified_at' => now(), - 'context' => 'training-participant', + 'context' => UserContext::TrainingParticipant->value, ]); $this->call([ diff --git a/database/seeders/TestDataSeeder.php b/database/seeders/TestDataSeeder.php index fcf3982ac..ae36caf62 100644 --- a/database/seeders/TestDataSeeder.php +++ b/database/seeders/TestDataSeeder.php @@ -5,6 +5,7 @@ use App\Enums\OrganizationRole; use App\Enums\OrganizationType; use App\Enums\ProvinceOrTerritory; +use App\Enums\UserContext; use App\Models\Engagement; use App\Models\Identity; use App\Models\Impact; @@ -334,7 +335,7 @@ public function run(): void 'user' => [ 'name' => 'Jannet Chow', 'email' => 'chow@accessibilityexchange.ca', - 'context' => 'regulated-organization', + 'context' => UserContext::RegulatedOrganization->value, ], 'froSector' => 'Federally Regulated private sector', 'froDetails' => [ @@ -366,7 +367,7 @@ public function run(): void 'user' => [ 'name' => 'Murlio Durado', 'email' => 'md@accessibilityexchange.ca', - 'context' => 'regulated-organization', + 'context' => UserContext::RegulatedOrganization->value, ], 'froSector' => 'Government of Canada', 'froDetails' => [ @@ -398,7 +399,7 @@ public function run(): void 'user' => [ 'name' => 'Ali Selim', 'email' => 'aselim@accessibilityexchange.ca', - 'context' => 'regulated-organization', + 'context' => UserContext::RegulatedOrganization->value, ], 'froSector' => 'Government of Canada', 'froDetails' => [ @@ -438,7 +439,7 @@ public function run(): void 'user' => [ 'name' => 'Habib Alesi', 'email' => 'halesi@accessibilityexchange.ca', - 'context' => 'organization', + 'context' => UserContext::Organization->value, ], 'organization' => [ 'published_at' => now(), diff --git a/resources/lang/en.json b/resources/lang/en.json index 97d2d63e2..5a00f7f42 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -85,7 +85,6 @@ "accessibility and inclusion link title": "accessibility and inclusion link title", "Accessibility Consultant": "Accessibility Consultant", "Accessibility Consultant and Community Connector application": "Accessibility Consultant and Community Connector application", - "Accessibility Consultant notification setting": "Accessibility Consultant notification setting", "Accessibility Consultants": "Accessibility Consultants", "Accessibility Consultants could help you design consultations that are inclusive and accessible.": "Accessibility Consultants could help you design consultations that are inclusive and accessible.", "Accessibility Consultants — Individual": "Accessibility Consultants — Individual", @@ -174,6 +173,8 @@ "An engagement name must be provided in at least one language.": "An engagement name must be provided in at least one language.", "An engagement name must be provided in either English or French.": "An engagement name must be provided in either English or French.", "a network and is able to conduct effective outreach to people with disabilities and Deaf persons in particular geographic communities and social groups (for example, Indigenous communities).": "a network and is able to conduct effective outreach to people with disabilities and Deaf persons in particular geographic communities and social groups (for example, Indigenous communities).", + "A new engagement has been uploaded: [:engagement](:engagement_url) by [:projectable](:projectable_url)": "A new engagement has been uploaded: [:engagement](:engagement_url) by [:projectable](:projectable_url)", + "A new engagement has been uploaded on The Accessibility Exchange:": "A new engagement has been uploaded on The Accessibility Exchange:", "A new project": "A new project", "Anonymous participant": "Anonymous participant", "An organization and its users have been suspended.": "An organization and its users have been suspended.", @@ -212,10 +213,7 @@ "Are you sure you want to remove :language? Any translations that you’ve entered will be lost.": "Are you sure you want to remove :language? Any translations that you’ve entered will be lost.", "Are you sure you want to remove :member from :organization? You cannot undo this.": "Are you sure you want to remove :member from :organization? You cannot undo this.", "As a :organizationType, you can engage with individuals to get input for your projects.": "As a :organizationType, you can engage with individuals to get input for your projects.", - "As a Community Connector": "As a Community Connector", "As a Community Connector, :name can connect to:": "As a Community Connector, :name can connect to:", - "As a Consultation Participant": "As a Consultation Participant", - "As an accessibility consultant": "As an accessibility consultant", "As an Accessibility Consultant, :name can help with:": "As an Accessibility Consultant, :name can help with:", "As an Accessibility Consultant, we can help with:": "As an Accessibility Consultant, we can help with:", "As an individual with a disability, Deaf person, or a supporter, you can participate in consultations by organizations and businesses who are working on accessibility projects and get paid for this. You can also gain access to resources and training on how to do this.": "As an individual with a disability, Deaf person, or a supporter, you can participate in consultations by organizations and businesses who are working on accessibility projects and get paid for this. You can also gain access to resources and training on how to do this.", @@ -317,6 +315,7 @@ "Change login email": "Change login email", "Change password": "Change password", "Change the colour of the text and background.": "Change the colour of the text and background.", + "Check it out.": "Check it out.", "Cheque": "Cheque", "Children (under 15)": "Children (under 15)", "Choices for: :question": "Choices for: :question", @@ -352,7 +351,6 @@ "Communities you can connect to": "Communities you can connect to", "Communities your organization :represents_or_serves_and_supports": "Communities your organization :represents_or_serves_and_supports", "Community Connector": "Community Connector", - "Community Connector notification setting": "Community Connector notification setting", "Community Connectors": "Community Connectors", "Community Connectors could help you connect with groups that may be hard to reach otherwise.": "Community Connectors could help you connect with groups that may be hard to reach otherwise.", "Community Connectors — Individual": "Community Connectors — Individual", @@ -547,10 +545,8 @@ "Edit resource collection": "Edit resource collection", "Edit roles": "Edit roles", "Edit user’s role": "Edit user’s role", - "Edit your contact information": "Edit your contact information", "Edit your individual page": "Edit your individual page", "Edit your organization page": "Edit your organization page", - "Edit your organization’s contact information": "Edit your organization’s contact information", "Edit your participant selection criteria": "Edit your participant selection criteria", "Edit your project page": "Edit your project page", "Edit your role": "Edit your role", @@ -585,7 +581,7 @@ "Engagements I’ve joined": "Engagements I’ve joined", "Engagements I’ve joined as a Community Connector": "Engagements I’ve joined as a Community Connector", "Engagements I’ve joined as a Consultation Participant": "Engagements I’ve joined as a Consultation Participant", - "Engagements that are looking for people that my organization represents or supports": "Engagements that are looking for people that my organization represents or supports", + "engagements notification setting": "engagements notification setting", "Engagements that are looking for someone with my lived experience": "Engagements that are looking for someone with my lived experience", "Engagement translations": "Engagement translations", "Engage with disability and Deaf communities and hold meaningful consultations": "Engage with disability and Deaf communities and hold meaningful consultations", @@ -691,11 +687,11 @@ "Glossary": "Glossary", "Go through our listings of Community Connectors on this website.": "Go through our listings of Community Connectors on this website.", "Go to all engagements": "Go to all engagements", + "Go to new engagement": "Go to new engagement", "Go to pricing": "Go to pricing", "Go to published page": "Go to published page", "Government": "Government", "Government of Canada": "Government of Canada", - "Governments, businesses, and other public sector organizations": "Governments, businesses, and other public sector organizations", "Grey on dark grey": "Grey on dark grey", "Grey on white": "Grey on white", "Groups in the disability and Deaf community": "Groups in the disability and Deaf community", @@ -785,8 +781,6 @@ "Individual orientation": "Individual orientation", "Individuals": "Individuals", "individuals": "individuals", - "Information such as your communication and consultation preferences might be out of date if it has not been updated for over a year.": "Information such as your communication and consultation preferences might be out of date if it has not been updated for over a year.", - "Information such as your matching information, your communication preferences, and your consultation preferences might be out of date if it has not been updated for over a year.": "Information such as your matching information, your communication preferences, and your consultation preferences might be out of date if it has not been updated for over a year.", "Information technology": "Information technology", "Information that we ask Consultation Participants, Accessibility Consultants, and Community Connectors to share.": "Information that we ask Consultation Participants, Accessibility Consultants, and Community Connectors to share.", "Initiated by": "Initiated by", @@ -823,7 +817,6 @@ "Joined as a Community Connector": "Joined as a Community Connector", "Joined as a Consultation Participant": "Joined as a Consultation Participant", "Join our accessibility community": "Join our accessibility community", - "Keeping my information up to date": "Keeping my information up to date", "Language": "Language", "language": "language", "Language: :locale": "Language: :locale", @@ -962,6 +955,8 @@ "Needs printed version to be sent to:": "Needs printed version to be sent to:", "Neurodivergence": "Neurodivergence", "New Brunswick": "New Brunswick", + "New engagement added": "New engagement added", + "New Engagement from :projectable": "New Engagement from :projectable", "New estimate approval": "New estimate approval", "New Estimate Approval from :projectable": "New Estimate Approval from :projectable", "New estimate request": "New estimate request", @@ -971,7 +966,6 @@ "new language": "new language", "New password": "New password", "New project": "New project", - "New reports uploaded": "New reports uploaded", "Next": "Next", "Next steps": "Next steps", "No": "No", @@ -1013,7 +1007,6 @@ "Notification List": "Notification List", "Notification preferences": "Notification preferences", "Notifications": "Notifications", - "Notify me or my support person directly": "Notify me or my support person directly", "Notify your organization’s contact person directly": "Notify your organization’s contact person directly", "Notify your organization’s team through the website": "Notify your organization’s team through the website", "Not in this project": "Not in this project", @@ -1128,7 +1121,6 @@ "Participate in consultations": "Participate in consultations", "Participate in consultations for organizations and businesses who are working on accessibility projects, and get paid for your participation.": "Participate in consultations for organizations and businesses who are working on accessibility projects, and get paid for your participation.", "Participating": "Participating", - "Participating in engagements": "Participating in engagements", "Pause": "Pause", "Payment": "Payment", "Payment information": "Payment information", @@ -1189,8 +1181,6 @@ "Please include any links that describes the accessibility and inclusion initiatives your regulated entity has. This can include reports, case studies, and more.": "Please include any links that describes the accessibility and inclusion initiatives your regulated entity has. This can include reports, case studies, and more.", "Please indicate how you would like to be notified of a new person or people being added to your engagements.": "Please indicate how you would like to be notified of a new person or people being added to your engagements.", "Please indicate how you would like to be notified of a project estimate that has been returned for you to review.": "Please indicate how you would like to be notified of a project estimate that has been returned for you to review.", - "Please indicate how you would like to be notified of new projects.": "Please indicate how you would like to be notified of new projects.", - "Please indicate how you would like to be notified to review and update your information.": "Please indicate how you would like to be notified to review and update your information.", "Please indicate if the reports will be publicly available.": "Please indicate if the reports will be publicly available.", "Please indicate if this is a new project or a progress report for an existing project.": "Please indicate if this is a new project or a progress report for an existing project.", "Please indicate the geographical areas this project will impact.": "Please indicate the geographical areas this project will impact.", @@ -1205,10 +1195,6 @@ "Please indicate whether your Community Connector is an individual or community organization.": "Please indicate whether your Community Connector is an individual or community organization.", "Please indicate which areas of your organization this project will impact.": "Please indicate which areas of your organization this project will impact.", "Please indicate which groups you can help organizations connect to. An organization may request the services of a Community Connector to assist them in connecting to these groups.": "Please indicate which groups you can help organizations connect to. An organization may request the services of a Community Connector to assist them in connecting to these groups.", - "Please indicate which type of engagements you would like to be notified about.": "Please indicate which type of engagements you would like to be notified about.", - "Please indicate which type of organizations’ projects you would like to notified about.": "Please indicate which type of organizations’ projects you would like to notified about.", - "Please indicate which type of projects or engagements you would like to be notified about.": "Please indicate which type of projects or engagements you would like to be notified about.", - "Please indicate which type of projects you would like to notified about.": "Please indicate which type of projects you would like to notified about.", "Please indicate who will be going through the results": "Please indicate who will be going through the results", "Please invite others so you can work on projects together.": "Please invite others so you can work on projects together.", "Please list any languages that you will be using to describe your organization.": "Please list any languages that you will be using to describe your organization.", @@ -1259,7 +1245,6 @@ "Preferred contact method": "Preferred contact method", "preferred contact method": "preferred contact method", "Preferred contact person": "Preferred contact person", - "Preferred notification method": "Preferred notification method", "present": "present", "Preview page": "Preview page", "previous project": "previous project", @@ -1277,11 +1262,9 @@ "projectable type": "projectable type", "Project by :projectable": "Project by :projectable", "project context": "project context", - "project created by organization type notification setting": "project created by organization type notification setting", "Project details": "Project details", "Project duration": "Project duration", "Project end date": "Project end date", - "project engagement type notification setting": "project engagement type notification setting", "Project goals": "Project goals", "project goals": "project goals", "Project goals (English)": "Project goals (English)", @@ -1311,14 +1294,11 @@ "Project scope must be provided in either English or French.": "Project scope must be provided in either English or French.", "Projects I’m running": "Projects I’m running", "Projects my organization has created": "Projects my organization has created", - "projects notification setting": "projects notification setting", "Project start date": "Project start date", - "Projects that are looking for people that my organization represents or supports": "Projects that are looking for people that my organization represents or supports", "Projects that are looking for someone with my lived experience": "Projects that are looking for someone with my lived experience", "Project team": "Project team", "Project timeframe": "Project timeframe", "Project Translations": "Project Translations", - "project type notification setting": "project type notification setting", "Pronouns": "Pronouns", "pronouns": "pronouns", "Provides Guidance and Resources": "Provides Guidance and Resources", @@ -1388,7 +1368,6 @@ "Remove this link": "Remove this link", "Remove this location": "Remove this location", "Remove this training": "Remove this training", - "report notification setting": "report notification setting", "represent": "represent", "Representative organization": "Representative organization", "Representative organizations": "Representative organizations", @@ -1416,7 +1395,6 @@ "return to engagement": "return to engagement", "Review and publish engagement details": "Review and publish engagement details", "Review and publish your organization’s public page": "Review and publish your organization’s public page", - "review and updates notification settings": "review and updates notification settings", "Review engagement details": "Review engagement details", "Review project": "Review project", "Review project details": "Review project details", @@ -1700,8 +1678,6 @@ "This will show up once you pick your role.": "This will show up once you pick your role.", "Through contacting me or my support person": "Through contacting me or my support person", "Throughout this page, you can choose whether you would like notifications to be sent through the website or by contacting the project team contact for that specific project directly. You can edit this in each individual project page.": "Throughout this page, you can choose whether you would like notifications to be sent through the website or by contacting the project team contact for that specific project directly. You can edit this in each individual project page.", - "Throughout this page, you can chose whether you would like notifications to be sent through the website or by contacting :contact_person directly.": "Throughout this page, you can chose whether you would like notifications to be sent through the website or by contacting :contact_person directly.", - "Throughout this page, you can chose whether you would like notifications to be sent through the website or by contacting your organization’s contact person directly. You’ve provided the following contact information:": "Throughout this page, you can chose whether you would like notifications to be sent through the website or by contacting your organization’s contact person directly. You’ve provided the following contact information:", "Through the website": "Through the website", "Thursday": "Thursday", "Time": "Time", @@ -1811,7 +1787,6 @@ "We will ask you about whether you have a preference for either in-person or virtual meetings. We will also ask you how you would like us to contact you, and for your contact information.": "We will ask you about whether you have a preference for either in-person or virtual meetings. We will also ask you how you would like us to contact you, and for your contact information.", "We will ask you for your preferred method of payment.": "We will ask you for your preferred method of payment.", "We will ask you to indicate:": "We will ask you to indicate:", - "We will notify you about being invited to engagements by directly contacting you or your support person.": "We will notify you about being invited to engagements by directly contacting you or your support person.", "What age group are you interested in engaging?": "What age group are you interested in engaging?", "what age groups you can connect to": "what age groups you can connect to", "What areas of accessibility planning and design are you most interested in consulting on?": "What areas of accessibility planning and design are you most interested in consulting on?", @@ -1887,9 +1862,7 @@ "Working age adults (15–64)": "Working age adults (15–64)", "Working languages": "Working languages", "Workshop": "Workshop", - "Would you like to be notified directly when a project you have worked on uploads a new report?": "Would you like to be notified directly when a project you have worked on uploads a new report?", - "Would you like to be notified directly when you are added as an Accessibility Consultant to a project?": "Would you like to be notified directly when you are added as an Accessibility Consultant to a project?", - "Would you like to be notified directly when you are added to an engagement as a Community Connector?": "Would you like to be notified directly when you are added to an engagement as a Community Connector?", + "Would you like to get notifications of new engagements?": "Would you like to get notifications of new engagements?", "Writing": "Writing", "Writing accessibility reports": "Writing accessibility reports", "written language for translation": "written language for translation", @@ -1907,7 +1880,6 @@ "Yes, project reports will be publicly available.": "Yes, project reports will be publicly available.", "Yes, share my access needs": "Yes, share my access needs", "Yes, some": "Yes, some", - "you": "you", "You already belong to an organization, so you cannot create a new one.": "You already belong to an organization, so you cannot create a new one.", "You are now able to publish your page.": "You are now able to publish your page.", "You are now able to publish your page and create projects and engagements.": "You are now able to publish your page and create projects and engagements.", @@ -2008,9 +1980,6 @@ "You must belong to an :organization in order to manage its roles and permissions.": "You must belong to an :organization in order to manage its roles and permissions.", "You must choose at least one payment type.": "You must choose at least one payment type.", "You must choose at least one province or territory.": "You must choose at least one province or territory.", - "You must choose at least one type of engagement.": "You must choose at least one type of engagement.", - "You must choose at least one type of organization.": "You must choose at least one type of organization.", - "You must choose at least one type of project.": "You must choose at least one type of project.", "You must choose a valid province or territory": "You must choose a valid province or territory", "You must enter a :attribute": "You must enter a :attribute", "You must enter a :attribute.": "You must enter a :attribute.", @@ -2144,14 +2113,12 @@ "Your regulated organization, :name, will be deleted and cannot be recovered. If you still want to delete your regulated organization, please enter your current password to proceed.": "Your regulated organization, :name, will be deleted and cannot be recovered. If you still want to delete your regulated organization, please enter your current password to proceed.", "Your roles have been saved.": "Your roles have been saved.", "Your signed agreement has been received": "Your signed agreement has been received", - "your support person, :name": "your support person, :name", "Your support person’s name is required if they are your preferred contact person.": "Your support person’s name is required if they are your preferred contact person.", "Your website accessibility preferences have been updated.": "Your website accessibility preferences have been updated.", "You scored :score%. Please try again.": "You scored :score%. Please try again.", "You sent this request on :date.": "You sent this request on :date.", "Youth (15–30)": "Youth (15–30)", "YouTube page": "YouTube page", - "You will always get a notification on the website.": "You will always get a notification on the website.", "You will be able to edit your information and browse projects and people on this site again.": "You will be able to edit your information and browse projects and people on this site again.", "You will be able to edit your information and browse projects and people on this site again. Your page will no longer be hidden to other users of the website.": "You will be able to edit your information and browse projects and people on this site again. Your page will no longer be hidden to other users of the website.", "you will no longer be able to access information about the :count engagements you are participating in": "you will no longer be able to access information about the :count engagements you are participating in", @@ -2167,9 +2134,8 @@ "You’ve blocked :individual. If you want to visit this page, you can :unblock and return to this page.": "You’ve blocked :individual. If you want to visit this page, you can :unblock and return to this page.", "You’ve blocked :organization. If you want to visit this page, you can :unblock and return to this page.": "You’ve blocked :organization. If you want to visit this page, you can :unblock and return to this page.", "You’ve blocked :regulatedOrganization. If you want to visit this page, you can :unblock and return to this page.": "You’ve blocked :regulatedOrganization. If you want to visit this page, you can :unblock and return to this page.", - "You’ve provided the following contact information:": "You’ve provided the following contact information:", - "You’ve provided the following contact information for them:": "You’ve provided the following contact information for them:", "Yukon Territory": "Yukon Territory", + "[:engagement](:engagement_url) by [:projectable](:projectable_url)": "[:engagement](:engagement_url) by [:projectable](:projectable_url)", "[:projectable](:projectable_url) has approved an estimate for their project [:project](:project_url).": "[:projectable](:projectable_url) has approved an estimate for their project [:project](:project_url).", "[:projectable](:projectable_url) has requested an estimate for their project [:project](:project_url).": "[:projectable](:projectable_url) has requested an estimate for their project [:project](:project_url).", "{1} :count engagement matches your applied filters.": "{1} :count engagement matches your applied filters.", diff --git a/resources/lang/fr.json b/resources/lang/fr.json index 0b7d063f1..7200c2474 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -85,7 +85,6 @@ "accessibility and inclusion link title": "titre du lien sur les mesures d’accessibilité et d’inclusion", "Accessibility Consultant": "Personne consultante en matière d’accessibilité", "Accessibility Consultant and Community Connector application": "Demande à titre de personne consultante en matière d’accessibilité et de personne facilitatrice communautaire", - "Accessibility Consultant notification setting": "Réglages des notifications à titre de personne consultante en matière d’accessibilité", "Accessibility Consultants": "Personnes consultantes en matière d’accessibilité", "Accessibility Consultants could help you design consultations that are inclusive and accessible.": "Les personnes consultantes en matière d’accessibilité peuvent vous aider à concevoir des consultations qui soient inclusives et accessibles.", "Accessibility Consultants — Individual": "Personne consultante en matière d’accessibilité - Individu", @@ -174,6 +173,8 @@ "An engagement name must be provided in at least one language.": "Le nom de la consultation doit être fourni dans au moins une langue.", "An engagement name must be provided in either English or French.": "Le nom de la consultation doit être fourni en anglais ou en français.", "a network and is able to conduct effective outreach to people with disabilities and Deaf persons in particular geographic communities and social groups (for example, Indigenous communities).": "un réseau et est capable de mener des actions de sensibilisation efficaces auprès des personnes en situation de handicap et des personnes sourdes dans des communautés géographiques et des groupes particuliers (par exemple, les communautés autochtones).", + "A new engagement has been uploaded: [:engagement](:engagement_url) by [:projectable](:projectable_url)": "", + "A new engagement has been uploaded on The Accessibility Exchange:": "", "A new project": "Un nouveau projet", "Anonymous participant": "Personne participante anonyme", "An organization and its users have been suspended.": "Une organisation et ses membres ont été suspendus.", @@ -212,10 +213,7 @@ "Are you sure you want to remove :language? Any translations that you’ve entered will be lost.": "Êtes-vous sûr de vouloir supprimer :language ? Toutes les traductions que vous avez saisies seront perdues.", "Are you sure you want to remove :member from :organization? You cannot undo this.": "Êtes-vous sûr de vouloir supprimer :member de votre « :organization » ? Vous ne pourrez pas annuler cette action.", "As a :organizationType, you can engage with individuals to get input for your projects.": "En tant que :organizationType, vous pouvez entrer en contact avec des individus pour obtenir des commentaires en lien avec vos projets.", - "As a Community Connector": "En tant que personne facilitatrice communautaire", "As a Community Connector, :name can connect to:": "En tant que personne facilatrice communautaire, :name peut agir comme intermédiaire auprès de :", - "As a Consultation Participant": "En tant que personne participante à une consultation", - "As an accessibility consultant": "En tant que personne consultante en matière d’accessibilité", "As an Accessibility Consultant, :name can help with:": "En tant que personne consultante en matière d’accessibilité, :name peut vous aider pour la :", "As an Accessibility Consultant, we can help with:": "En tant que personne consultante en matière d’accessibilité, nous pouvons vous aider à :", "As an individual with a disability, Deaf person, or a supporter, you can participate in consultations by organizations and businesses who are working on accessibility projects and get paid for this. You can also gain access to resources and training on how to do this.": "En tant que personne en situation de handicap, ou personne sourde, ou encore personne fournissant du soutien à une personne en situation de handicap ou sourde, vous pouvez participer et être payée pour participer à des consultations en matière d’accessibilité organisées par des organisations ou des entreprises sous réglementation fédérale. Vous avez également accès à des ressources et à des formations sur comment accomplir ces tâches.", @@ -317,6 +315,7 @@ "Change login email": "Changer l’identifiant courriel", "Change password": "Changer le mot de passe", "Change the colour of the text and background.": "Changer la couleur du texte et de l’arrière-plan.", + "Check it out.": "", "Cheque": "Chèque", "Children (under 15)": "Enfants (moins de 15 ans)", "Choices for: :question": "Choix pour :question", @@ -352,7 +351,6 @@ "Communities you can connect to": "Communautés auprès desquelles vous pouvez agir comme intermédiaire", "Communities your organization :represents_or_serves_and_supports": "Communautés que votre organisation :represents_or_serves_and_supports", "Community Connector": "Personne facilitatrice communautaire", - "Community Connector notification setting": "Réglages des notifications à titre de personne facilitatrice communautaire", "Community Connectors": "Personnes facilitatrices communautaires", "Community Connectors could help you connect with groups that may be hard to reach otherwise.": "Les personnes facilitatrices communautaires peuvent vous aider à entrer en contact avec des groupes qui seraient autrement difficiles à rejoindre.", "Community Connectors — Individual": "Personne facilitatrice communautaire - Individu", @@ -547,10 +545,8 @@ "Edit resource collection": "Éditer une bibliothèque de ressources", "Edit roles": "Modifier les rôles", "Edit user’s role": "Changer le rôle de la personne", - "Edit your contact information": "Modifier vos informations de contact", "Edit your individual page": "Modifier votre page personnelle", "Edit your organization page": "Modifier la page de votre organisation", - "Edit your organization’s contact information": "Modifier les informations de contact de votre organisation", "Edit your participant selection criteria": "Modifier les critères de sélection des personnes participantes", "Edit your project page": "Modifier la page de votre projet", "Edit your role": "Modifier votre rôle", @@ -585,7 +581,7 @@ "Engagements I’ve joined": "Consultations auxquelles je participe", "Engagements I’ve joined as a Community Connector": "Consultations auxquelles je participe à titre de personne facilitratrice communautaire", "Engagements I’ve joined as a Consultation Participant": "Consultations auxquelles je participe comme personne participant à des consultations", - "Engagements that are looking for people that my organization represents or supports": "Consultations qui recherchent des personnes que mon organisation représente ou soutient", + "engagements notification setting": "", "Engagements that are looking for someone with my lived experience": "Consultations qui sont à la recherche de personnes avec mon expérience vécue", "Engagement translations": "Traductions de la consultation", "Engage with disability and Deaf communities and hold meaningful consultations": "Collaborez avec les communautés des personnes en situation de handicap et des personnes sourdes et tenez des consultations substantielles", @@ -691,11 +687,11 @@ "Glossary": "Glossaire", "Go through our listings of Community Connectors on this website.": "Parcourez nos listes des personnes facilitatrices communautaires sur ce site Internet.", "Go to all engagements": "Voir toutes les consultations", + "Go to new engagement": "", "Go to pricing": "Accéder aux tarifs", "Go to published page": "Accéder à la page publiée", "Government": "Gouvernement", "Government of Canada": "Gouvernement du Canada", - "Governments, businesses, and other public sector organizations": "Les gouvernements, les entreprises et autres organisations du secteur public", "Grey on dark grey": "Gris sur gris foncé", "Grey on white": "Gris sur blanc", "Groups in the disability and Deaf community": "Groupes de la communauté des personnes en situation de handicap et des personnes sourdes", @@ -785,8 +781,6 @@ "Individual orientation": "Individual orientation", "Individuals": "Individus", "individuals": "individus", - "Information such as your communication and consultation preferences might be out of date if it has not been updated for over a year.": "Certaines informations telles que vos préférences en matière de communication et de consultation pourraient être obsolètes si elle n’ont pas été mises à jour depuis plus d’un an.", - "Information such as your matching information, your communication preferences, and your consultation preferences might be out of date if it has not been updated for over a year.": "Certaines informations telles que vos informations pour le jumelage, vos préférences en matière de communication et de consultation pourraient être obsolètes si elle n’ont pas été mises à jour depuis plus d’un an.", "Information technology": "Technologies de l’information", "Information that we ask Consultation Participants, Accessibility Consultants, and Community Connectors to share.": "Informations que nous demandons aux personnes participant aux consultations, aux personnes consultantes en matière d’accessibilité et aux personnes facilitatrices communautaires de partager.", "Initiated by": "Lancée par", @@ -823,7 +817,6 @@ "Joined as a Community Connector": "Participation à titre de personne facilitratrice communautaire", "Joined as a Consultation Participant": "Participation à titre de personne participant à des consultations", "Join our accessibility community": "Rejoignez notre communauté en faveur de l’accessibilité", - "Keeping my information up to date": "Maintenir mes informations à jour", "Language": "Langue", "language": "langue", "Language: :locale": "Langue: :locale", @@ -962,6 +955,8 @@ "Needs printed version to be sent to:": "A besoin d’une version imprimée envoyée à :", "Neurodivergence": "Neurodiversité", "New Brunswick": "Nouveau-Brunswick", + "New engagement added": "", + "New Engagement from :projectable": "", "New estimate approval": "Nouvelle approbation du devis", "New Estimate Approval from :projectable": "Nouvelle approbation de devis de :projectable", "New estimate request": "Nouvelle demande de devis", @@ -971,7 +966,6 @@ "new language": "ajouter une langue", "New password": "Nouveau mot de passe", "New project": "Nouveau projet", - "New reports uploaded": "Nouveaux rapports téléversés", "Next": "Suivant", "Next steps": "Prochaines étapes", "No": "Non", @@ -1013,7 +1007,6 @@ "Notification List": "Liste des notifications", "Notification preferences": "Préférences de notification", "Notifications": "Notifications", - "Notify me or my support person directly": "Avisez-moi ou avisez la personne me fournissant du soutien directement", "Notify your organization’s contact person directly": "Prévenez directement la personne de contact de votre organisation", "Notify your organization’s team through the website": "Informez l’équipe de votre organisation par le biais du site Internet", "Not in this project": "Sujets non traités dans ce projet", @@ -1128,7 +1121,6 @@ "Participate in consultations": "Participe à des consultations sur l’accessibilité", "Participate in consultations for organizations and businesses who are working on accessibility projects, and get paid for your participation.": "Participer à des consultations pour des organisations et des entreprises qui travaillent sur des projets relatifs à l’accessibilité et être payé pour votre participation.", "Participating": "Participating", - "Participating in engagements": "Participer à des consultations", "Pause": "Pause", "Payment": "Paiement", "Payment information": "Informations de paiement", @@ -1189,8 +1181,6 @@ "Please include any links that describes the accessibility and inclusion initiatives your regulated entity has. This can include reports, case studies, and more.": "Veuillez inclure tout lien qui décrit les initiatives en matière d’accessibilité et d’inclusion de votre entité réglementée. Cela peut inclure des rapports, des études de cas, et plus encore.", "Please indicate how you would like to be notified of a new person or people being added to your engagements.": "Veuillez indiquer comment vous souhaitez être informé de l’ajout d’une ou plusieurs nouvelles personnes à vos consultations.", "Please indicate how you would like to be notified of a project estimate that has been returned for you to review.": "Veuillez indiquer comment vous aimeriez être informé qu’un devis pour un projet vous a été retourné.", - "Please indicate how you would like to be notified of new projects.": "Veuillez indiquer comment vous souhaitez être informé de tout nouveau projet.", - "Please indicate how you would like to be notified to review and update your information.": "Veuillez indiquer comment vous souhaitez être contacté lorsque vient le temps de réviser et mettre à jour vos informations.", "Please indicate if the reports will be publicly available.": "Veuillez indiquer si les rapports seront accessibles au public.", "Please indicate if this is a new project or a progress report for an existing project.": "Veuillez indiquer s’il s’agit d’un nouveau projet ou d’un rapport d’étape pour un projet existant.", "Please indicate the geographical areas this project will impact.": "Veuillez indiquer les zones géographiques sur lesquelles ce projet aura un impact.", @@ -1205,10 +1195,6 @@ "Please indicate whether your Community Connector is an individual or community organization.": "Veuillez indiquer si votre personne facilitatrice communautaire est une personne ou une organisation communautaire.", "Please indicate which areas of your organization this project will impact.": "Veuillez indiquer les domaines de votre organisation sur lesquels ce projet aura un impact.", "Please indicate which groups you can help organizations connect to. An organization may request the services of a Community Connector to assist them in connecting to these groups.": "Veuillez indiquer les groupes auprès desquels vous pouvez agir comme intermédiaire. Les personnes facilitatrices permettent aux organisation réglementées d’avoir de l’aide pour rejoindre des groupes parfois plus difficiles à rejoindre.", - "Please indicate which type of engagements you would like to be notified about.": "Veuillez indiquer les types de consultations pour lesquelles vous souhaiteriez être notifié.", - "Please indicate which type of organizations’ projects you would like to notified about.": "Veuillez indiquer le type de projets d’organisations pour lesquels vous souhaiteriez recevoir une notification.", - "Please indicate which type of projects or engagements you would like to be notified about.": "Veuillez indiquer le type de projets ou de consultations pour lesquels vous souhaiteriez recevoir une notification.", - "Please indicate which type of projects you would like to notified about.": "Veuillez indiquer le type de projets pour lesquels vous souhaiteriez recevoir une notification.", "Please indicate who will be going through the results": "Veuillez indiquer qui analysera les résultats", "Please invite others so you can work on projects together.": "Veuillez inviter d’autres personnes pour que vous puissiez travailler ensemble sur des projets.", "Please list any languages that you will be using to describe your organization.": "Veuillez énumérer toutes les langues que vous utiliserez pour décrire votre organisation.", @@ -1259,7 +1245,6 @@ "Preferred contact method": "Méthode de contact privilégiée", "preferred contact method": "méthode de contact privilégiée", "Preferred contact person": "Personne de contact privilégiée", - "Preferred notification method": "Méthode de notification privilégiée", "present": "présent", "Preview page": "Prévisualiser la page", "previous project": "projet précédent", @@ -1277,11 +1262,9 @@ "projectable type": "projectable type", "Project by :projectable": "Projet par :projectable", "project context": "contexte du projet", - "project created by organization type notification setting": "project created by organization type notification setting", "Project details": "Détails du projet", "Project duration": "Durée du projet", "Project end date": "Date de fin du projet", - "project engagement type notification setting": "project engagement type notification setting", "Project goals": "Objectifs du projet", "project goals": "objectifs du projet", "Project goals (English)": "Objectifs du projet (anglais)", @@ -1311,14 +1294,11 @@ "Project scope must be provided in either English or French.": "La portée du projet doit être fournie en anglais ou en français.", "Projects I’m running": "Projets que je dirige", "Projects my organization has created": "Projets créés par mon organisation", - "projects notification setting": "paramètres des notifications pour le projet", "Project start date": "Date de début du projet", - "Projects that are looking for people that my organization represents or supports": "Projets cherchant des personnes que mon organisation représente ou soutient", "Projects that are looking for someone with my lived experience": "Projets cherchant une personne ayant une expérience vécue similaire à la mienne", "Project team": "Équipe du projet", "Project timeframe": "Echéancier du projet", "Project Translations": "Traductions du projet", - "project type notification setting": "type de notifications pour le projet", "Pronouns": "Pronons", "pronouns": "pronons", "Provides Guidance and Resources": "Un lieu pour trouver des conseils et des ressources", @@ -1388,7 +1368,6 @@ "Remove this link": "Retirer ce lien", "Remove this location": "Retirer cet emplacement", "Remove this training": "Retirer cette formation", - "report notification setting": "report notification setting", "represent": "représenter", "Representative organization": "Organisation représentative", "Representative organizations": "Organisation de personnes en situation de handicap ou de personnes sourdes", @@ -1416,7 +1395,6 @@ "return to engagement": "retourner à la consultation", "Review and publish engagement details": "Réviser et publier les détails de la consultation", "Review and publish your organization’s public page": "Révisez et publiez la page publique de votre organisation", - "review and updates notification settings": "review and updates notification settings", "Review engagement details": "Réviser les détails de la consultation", "Review project": "Réviser le projet", "Review project details": "Vérifier les détails du projet", @@ -1700,8 +1678,6 @@ "This will show up once you pick your role.": "Cela apparaîtra une fois que vous aurez choisi votre rôle.", "Through contacting me or my support person": "En me contactant ou en contactant la personne m’apportant du soutien", "Throughout this page, you can choose whether you would like notifications to be sent through the website or by contacting the project team contact for that specific project directly. You can edit this in each individual project page.": "Sur cette page, vous pouvez choisir si vous souhaitez que les notifications soient envoyées via le site Internet ou directement à la personne responsable du projet en question. Vous pouvez modifier ce réglage pour chaque projet individuellement.", - "Throughout this page, you can chose whether you would like notifications to be sent through the website or by contacting :contact_person directly.": "Sur cette page, vous pouvez choisir si vous souhaitez que les notifications soient envoyées via le site Internet ou directement à :contact_person.", - "Throughout this page, you can chose whether you would like notifications to be sent through the website or by contacting your organization’s contact person directly. You’ve provided the following contact information:": "Sur cette page, vous pouvez choisir si vous souhaitez que les notifications soient envoyées via le site Internet ou directement à la personne contact de votre organisation. Vous avez fourni les informations de contact suivantes :", "Through the website": "Sur le site", "Thursday": "Jeudi", "Time": "Heure", @@ -1811,7 +1787,6 @@ "We will ask you about whether you have a preference for either in-person or virtual meetings. We will also ask you how you would like us to contact you, and for your contact information.": "Nous vous demanderons si vous avez une préférence pour les réunions en personne ou virtuelles. Nous vous demanderons également comment vous souhaitez que nous vous contactions, ainsi que vos coordonnées.", "We will ask you for your preferred method of payment.": "Nous vous demanderons votre mode de paiement préféré.", "We will ask you to indicate:": "Nous vous demanderons d’indiquer :", - "We will notify you about being invited to engagements by directly contacting you or your support person.": "Nous vous informerons des invitations à des consultations en vous contactant directement ou en contactant votre personne de soutien.", "What age group are you interested in engaging?": "Quel est le groupe d’âge que vous souhaitez impliquer ?", "what age groups you can connect to": "les groupes d’âges auprès desquels vous pouvez servir d’intermédiaire", "What areas of accessibility planning and design are you most interested in consulting on?": "Quels sont les domaines de la planification et de la conception de plans d’accessibilité qui vous intéressent le plus ?", @@ -1887,9 +1862,7 @@ "Working age adults (15–64)": "Adultes en âge de travailler (15-64)", "Working languages": "Langues de travail", "Workshop": "Atelier", - "Would you like to be notified directly when a project you have worked on uploads a new report?": "Souhaitez-vous être averti dès qu’un projet sur lequel vous avez travaillé met en ligne un nouveau rapport ?", - "Would you like to be notified directly when you are added as an Accessibility Consultant to a project?": "Souhaitez-vous être averti lorsque vous êtes ajouté en tant que personne consultante en matière d’accessibilité à un projet ?", - "Would you like to be notified directly when you are added to an engagement as a Community Connector?": "Aimeriez vous recevoir une notification lorsque vous êtes ajouté à une consultation en tant que personne facilitatrice communautaire ?", + "Would you like to get notifications of new engagements?": "", "Writing": "Réponse écrite", "Writing accessibility reports": "Rédaction de rapports relatifs à l’accessibilité", "written language for translation": "traduction en langue écrite", @@ -1907,7 +1880,6 @@ "Yes, project reports will be publicly available.": "Les rapports du projet seront accessibles au public.", "Yes, share my access needs": "Oui, vous pouvez partager mes besoins en matière d’accessibilité", "Yes, some": "Oui, pour certaines des réponses sélectionnées", - "you": "vous", "You already belong to an organization, so you cannot create a new one.": "Vous faites déjà partie d’une organisation, vous ne pouvez donc pas en créer une nouvelle.", "You are now able to publish your page.": "Vous êtes maintenant en mesure de publier votre page.", "You are now able to publish your page and create projects and engagements.": "Vous êtes maintenant en mesure de publier votre page ainsi que vos consultations.", @@ -2008,9 +1980,6 @@ "You must belong to an :organization in order to manage its roles and permissions.": "Vous devez appartenir à une :organization afin de pouvoir gérer ses rôles et autorisations.", "You must choose at least one payment type.": "Vous devez choisir au moins un type de paiement.", "You must choose at least one province or territory.": "Vous devez choisir au moins une province ou un territoire.", - "You must choose at least one type of engagement.": "Vous devez choisir au moins un type de consultation.", - "You must choose at least one type of organization.": "Vous devez choisir au moins un type d’organisation.", - "You must choose at least one type of project.": "Vous devez choisir au moins un type de projet.", "You must choose a valid province or territory": "Vous devez indiquer une province ou un territoire", "You must enter a :attribute": "Vous devez rentrer un-e :attribute", "You must enter a :attribute.": "Vous devez rentrer un-e :attribute.", @@ -2144,14 +2113,12 @@ "Your regulated organization, :name, will be deleted and cannot be recovered. If you still want to delete your regulated organization, please enter your current password to proceed.": "Votre organisme réglementé, :name, sera supprimé et ne pourra pas être récupéré. Si vous souhaitez toujours supprimer votre organisme réglementé, veuillez saisir votre mot de passe actuel pour continuer.", "Your roles have been saved.": "Vos rôles ont été enregistrés.", "Your signed agreement has been received": "Votre entente signée a été reçue", - "your support person, :name": "la personne vous apportant du soutien, :name", "Your support person’s name is required if they are your preferred contact person.": "Le nom de la personne vous apportant du soutien est requis s’il s’agit de votre personne de contact.", "Your website accessibility preferences have been updated.": "Vos préférences en matière d’accessibilité pour le site Internet ont été mises à jour.", "You scored :score%. Please try again.": "Vous avez obtenu :score%. Veuillez réessayer.", "You sent this request on :date.": "Vous avez envoyé cette demande le :date.", "Youth (15–30)": "Jeunes (15-30)", "YouTube page": "Page YouTube", - "You will always get a notification on the website.": "Vous recevrez toujours une notification sur le site Internet.", "You will be able to edit your information and browse projects and people on this site again.": "Vous pourrez à nouveau modifier vos informations et parcourir les projets et les personnes sur ce site.", "You will be able to edit your information and browse projects and people on this site again. Your page will no longer be hidden to other users of the website.": "Vous pourrez à nouveau modifier vos informations et parcourir les projets et les personnes sur ce site. Votre page ne sera plus cachée aux autres utilisateurs du site.", "you will no longer be able to access information about the :count engagements you are participating in": "vous ne serez plus en mesure d’accéder aux informations sur les :count consultations auxquelles vous participez", @@ -2167,9 +2134,8 @@ "You’ve blocked :individual. If you want to visit this page, you can :unblock and return to this page.": "Vous avez bloqué :individual. Si vous souhaitez visiter leur page dans le futur, vous devrez les :unblock puis rafraichir la page.", "You’ve blocked :organization. If you want to visit this page, you can :unblock and return to this page.": "Vous avez bloqué :organization. Si vous souhaitez visiter leur page dans le futur, vous devrez les :unblock puis rafraichir la page.", "You’ve blocked :regulatedOrganization. If you want to visit this page, you can :unblock and return to this page.": "Vous avez bloqué :regulatedOrganization. Si vous souhaitez visiter leur page dans le futur, vous devrez les :unblock puis rafraichir la page.", - "You’ve provided the following contact information:": "Vous avez fourni les informations de contact suivantes :", - "You’ve provided the following contact information for them:": "Vous avez fourni les informations de contact suivantes pour cette personne :", "Yukon Territory": "Territoire du Yukon", + "[:engagement](:engagement_url) by [:projectable](:projectable_url)": "", "[:projectable](:projectable_url) has approved an estimate for their project [:project](:project_url).": "[:projectable](:projectable_url) a approuvé un devis pour leur projet [:project](:project_url).", "[:projectable](:projectable_url) has requested an estimate for their project [:project](:project_url).": "[:projectable](:projectable_url) a demandé un devis pour leur projet [:project](:project_url).", "{1} :count engagement matches your applied filters.": "{1} :count engagement matches your applied filters.", diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 4ab81405d..920b0b11c 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -53,15 +53,15 @@ @env('local') - - - - - @endenv diff --git a/resources/views/components/notification/engagement-added.blade.php b/resources/views/components/notification/engagement-added.blade.php new file mode 100644 index 000000000..8f817461f --- /dev/null +++ b/resources/views/components/notification/engagement-added.blade.php @@ -0,0 +1,8 @@ + + {{ $title }} + {{ $body }} + + {{ __('Go to new engagement') }} + + diff --git a/resources/views/mail/engagement-added.blade.php b/resources/views/mail/engagement-added.blade.php new file mode 100644 index 000000000..a5ed32fbe --- /dev/null +++ b/resources/views/mail/engagement-added.blade.php @@ -0,0 +1,18 @@ +@component('mail::message') +{{ __('A new engagement has been uploaded on The Accessibility Exchange:') }} +{{ + safe_markdown('[:engagement](:engagement_url) by [:projectable](:projectable_url)', [ + 'engagement' => $engagement->getTranslation('name', locale()), + 'engagement_url' => localized_route('engagements.show', $engagement), + 'projectable' => $projectable->getTranslation('name', locale()), + 'projectable_url' => localized_route($projectable->getRoutePrefix().'.show', $projectable), + ]) +}} + +
{{ __('Check it out.') }}
+@component('mail::button', ['url' => localized_route('engagements.show', $engagement)]) +{{ __('Go to new engagement') }} +@endcomponent + +{{ safe_markdown('They have been instructed to send their signed agreement to <:email>.', ['email' => settings_localized('email', locale())]) }} +@endcomponent diff --git a/resources/views/projects/edit/1.blade.php b/resources/views/projects/edit/1.blade.php index da379ecd1..371317210 100644 --- a/resources/views/projects/edit/1.blade.php +++ b/resources/views/projects/edit/1.blade.php @@ -109,7 +109,7 @@ {{ __('This can mean either on this website, or on your organization’s website.') }} - +
diff --git a/resources/views/projects/edit/2.blade.php b/resources/views/projects/edit/2.blade.php index 7df9ed6ce..1160cbc31 100644 --- a/resources/views/projects/edit/2.blade.php +++ b/resources/views/projects/edit/2.blade.php @@ -28,7 +28,7 @@ - - +
@csrf @method('put') -
-

{{ __('Contacting you with notifications') }}

- - -

- {{ __('Throughout this page, you can chose whether you would like notifications to be sent through the website or by contacting :contact_person directly.', ['contact_person' => $user->support_person_name ? __('your support person, :name', ['name' => $user->support_person_name]) : __('you')]) }} - @if ($user->support_person_name) - {{ __('You’ve provided the following contact information for them:') }} - @else - {{ __('You’ve provided the following contact information:') }} - @endif -

- - @if ($user->preferred_contact_person === 'me') - - @if ($user->phone) - - @endif - @else - @if ($user->support_person_email) - - @endif - @if ($user->support_person_phone) - - @endif - @endif - -

- @svg('heroicon-o-pencil', 'mr-1'){{ __('Edit your contact information') }} -

- -
- - {{ __('Preferred notification method') . ' ' . __('(required)') }} - - @if (!in_array('phone', $user->contact_methods)) - - @elseif(!in_array('email', $user->contact_methods)) - - @else - - @endif -
-
- -

{{ __('Participating in engagements') }}

- - - @if ($user->individual->isParticipant()) -

{{ __('As a Consultation Participant') }}

- - -

- {{ __('We will notify you about being invited to engagements by directly contacting you or your support person.') }} -

- @endif - - @if ($user->individual->isConsultant()) -
- {{ __('As an accessibility consultant') }} - - - {{ __('Would you like to be notified directly when you are added as an Accessibility Consultant to a project?') }}
- {{ __('You will always get a notification on the website.') }} -
-
- - - -
-
- @endif - - @if ($user->individual->isConnector()) -
- {{ __('As a Community Connector') }} - - - {{ __('Would you like to be notified directly when you are added to an engagement as a Community Connector?') }}
- {{ __('You will always get a notification on the website.') }} -
- - - -
- @endif - -
- {{ __('New reports uploaded') }} - - - {{ __('Would you like to be notified directly when a project you have worked on uploads a new report?') }}
- {{ __('You will always get a notification on the website.') }} -
- - - -
-

{{ __('Finding out about new projects') }}

-
-
- {{ __('Please indicate how you would like to be notified of new projects.') }} - - -
- -
- - {{ __('Please indicate which type of organizations’ projects you would like to notified about.') }} - - - {{ __('Please check all that apply.') }} - - -
- -
- {{ __('Please indicate which type of projects you would like to notified about.') }} - - - {{ __('Please check all that apply.') }} - - -
- -
- {{ __('Please indicate which type of engagements you would like to be notified about.') }} - - - {{ __('Please check all that apply.') }} - - -
-
- -

- {{ __('Keeping my information up to date') }}

- - -
- - {{ __('Please indicate how you would like to be notified to review and update your information.') }} - - + {{ __('Would you like to get notifications of new engagements?') }} + - @if ($user->individual->isParticipant()) -

{{ __('Information such as your matching information, your communication preferences, and your consultation preferences might be out of date if it has not been updated for over a year.') }} -

- @else -

{{ __('Information such as your communication and consultation preferences might be out of date if it has not been updated for over a year.') }} -

- @endif - + +
diff --git a/resources/views/settings/notifications/organization.blade.php b/resources/views/settings/notifications/organization.blade.php index 4f49728c0..2cbe4df29 100644 --- a/resources/views/settings/notifications/organization.blade.php +++ b/resources/views/settings/notifications/organization.blade.php @@ -1,141 +1,24 @@
- @csrf @method('put') -
-

{{ __('Contacting you with notifications') }}

- - -

- {{ __('Throughout this page, you can chose whether you would like notifications to be sent through the website or by contacting your organization’s contact person directly. You’ve provided the following contact information:') }} -

- -

{{ __('Name') }}: {{ $user->organization->contact_person_name }}

- @if ($user->organization->contact_person_email) - - @endif - @if ($user->organization->contact_person_phone) - - @endif - -

@svg('heroicon-o-pencil', 'mr-1') - {{ __('Edit your organization’s contact information') }} -

- -
- - {{ __('Preferred notification method') . ' ' . __('(required)') }} - - @if (!in_array('phone', $user->organization->contact_methods)) - - @elseif(!in_array('email', $user->organization->contact_methods)) - - @else - - @endif -
-
-

{{ __('Projects and engagements by other organizations') }}

-
-
- {{ __('Please indicate how you would like to be notified of new projects.') }} - - - -
- -
- - {{ __('Please indicate which type of organizations’ projects you would like to notified about.') }} - - - {{ __('Please check all that apply.') }} - - -
- -
- - {{ __('Please indicate which type of projects or engagements you would like to be notified about.') }} - - - {{ __('Please check all that apply.') }} -
- - -
-
- - -
-
-
+
+ {{ __('Would you like to get notifications of new engagements?') }} + + + +

diff --git a/tests/Datasets/UpdateNotificationPreferencesRequestValidationErrors.php b/tests/Datasets/UpdateNotificationPreferencesRequestValidationErrors.php new file mode 100644 index 000000000..547b1d9f3 --- /dev/null +++ b/tests/Datasets/UpdateNotificationPreferencesRequestValidationErrors.php @@ -0,0 +1,14 @@ + fn () => [ + 'state' => ['notification_settings' => ['engagements' => null]], + 'errors' => ['notification_settings.engagements' => __('validation.required', ['attribute' => __('engagements notification setting')])], + ], + 'Notification settings: engagements is not boolean' => fn () => [ + 'state' => ['notification_settings' => ['engagements' => 'invalid']], + 'errors' => ['notification_settings.engagements' => __('validation.boolean', ['attribute' => __('engagements notification setting')])], + ], + ]; +}); diff --git a/tests/Feature/AccessNeedsFacilitationNotificationTest.php b/tests/Feature/AccessNeedsFacilitationNotificationTest.php index c0fc239c2..c8e3ee2b1 100644 --- a/tests/Feature/AccessNeedsFacilitationNotificationTest.php +++ b/tests/Feature/AccessNeedsFacilitationNotificationTest.php @@ -1,6 +1,7 @@ admin = User::factory()->create([ 'email_verified_at' => now(), - 'context' => 'administrator', + 'context' => UserContext::Administrator->value, ]); $this->engagement = Engagement::factory()->create([ diff --git a/tests/Feature/AdministratorTest.php b/tests/Feature/AdministratorTest.php index 8d79a9b8e..928a56a16 100644 --- a/tests/Feature/AdministratorTest.php +++ b/tests/Feature/AdministratorTest.php @@ -1,12 +1,13 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); actingAs($user)->get(localized_route('admin.estimates-and-agreements')) ->assertRedirect(localized_route('dashboard')); @@ -16,7 +17,7 @@ }); test('log out from Filament redirects to standard login', function () { - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); actingAs($administrator)->from(route('filament.admin.resources.interpretations.index'))->post(route('filament.admin.auth.logout')) ->assertRedirect(localized_route('login')); diff --git a/tests/Feature/BlocklistTest.php b/tests/Feature/BlocklistTest.php index 5b020addd..3b849e552 100644 --- a/tests/Feature/BlocklistTest.php +++ b/tests/Feature/BlocklistTest.php @@ -1,5 +1,6 @@ get(localized_route('block-list.show')) ->assertOk(); - $regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); actingAs($regulatedOrganizationUser)->get(localized_route('block-list.show')) ->assertForbidden(); @@ -142,7 +143,7 @@ }); test('regulated organization member cannot block their regulated organization', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); @@ -156,7 +157,7 @@ }); test('organization member cannot block their organization', function () { - $user = User::factory()->create(['context' => 'organization']); + $user = User::factory()->create(['context' => UserContext::Organization->value]); $organization = Organization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); diff --git a/tests/Feature/Console/MigrateSettingsDataTest.php b/tests/Feature/Console/MigrateSettingsDataTest.php new file mode 100644 index 000000000..ed2b1cfcd --- /dev/null +++ b/tests/Feature/Console/MigrateSettingsDataTest.php @@ -0,0 +1,120 @@ +assertSuccessful() + ->expectsOutputToContain('Completed') + ->expectsOutputToContain('Skipped'); + + // clean up + artisan('migrate:fresh'); +}); + +test('app:migrate-settings-data command can list migrations', function () { + $this->artisan('app:migrate-settings-data --list') + ->assertSuccessful() + ->expectsOutputToContain('Version added'); + + // clean up + artisan('migrate:fresh'); +}); + +test('app:migrate-settings-data command can run migrations starting at a specific version', function () { + artisan('app:migrate-settings-data --from=1.0.0') + ->assertSuccessful() + ->expectsOutputToContain('Skipped 0') + ->doesntExpectOutputToContain('Completed 0'); + + artisan('app:migrate-settings-data --from=100000') + ->assertSuccessful() + ->expectsOutputToContain('Completed 0') + ->doesntExpectOutputToContain('Skipped 0'); + + // clean up + artisan('migrate:fresh'); +}); + +test('app:migrate-settings-data command can run with verbose logging', function () { + artisan('app:migrate-settings-data -v') + ->assertSuccessful() + ->expectsOutputToContain('Run migration -'); + + // clean up + artisan('migrate:fresh'); +}); + +test('enableEngagementNotificationsMigration - Migrates Individual users and orgs data successfully', function () { + $user = User::factory()->create([ + 'notification_settings' => ['other' => 'test'], + ]); + + $org = Organization::factory()->create([ + 'notification_settings' => ['other' => 'test'], + ]); + + artisan('app:migrate-settings-data')->assertSuccessful(); + + $user->refresh(); + expect($user->notification_settings->get('other'))->toBeNull(); + expect($user->notification_settings->get('engagements'))->toBe('1'); + expect($user->notification_settings->count())->toBe(1); + + $org->refresh(); + expect($org->notification_settings->get('other'))->toBeNull(); + expect($org->notification_settings->get('engagements'))->toBe('1'); + expect($org->notification_settings->count())->toBe(1); + + // clean up + artisan('migrate:fresh'); +}); + +test('enableEngagementNotificationsMigration - Only migrates Individual users and orgs', function () { + $user = User::factory()->create([ + 'context' => UserContext::Organization->value, + ]); + + $fro = RegulatedOrganization::factory()->create(); + + artisan('app:migrate-settings-data')->assertSuccessful(); + + $user->refresh(); + expect($user->notification_settings->get('engagements'))->toBeNull(); + expect($user->notification_settings->count())->toBe(0); + + $fro->refresh(); + expect($fro->notification_settings->get('engagements'))->toBeNull(); + expect($fro->notification_settings->count())->toBe(0); + + // clean up + artisan('migrate:fresh'); +}); + +test('enableEngagementNotificationsMigration - skips when notifications_settings are already updated', function () { + $user = User::factory()->create([ + 'notification_settings' => ['engagements' => '0'], + ]); + + $org = User::factory()->create([ + 'notification_settings' => ['engagements' => '0'], + ]); + + artisan('app:migrate-settings-data')->assertSuccessful(); + + $user->refresh(); + expect($user->notification_settings->get('engagements'))->toBe('0'); + expect($user->notification_settings->count())->toBe(1); + + $org->refresh(); + expect($org->notification_settings->get('engagements'))->toBe('0'); + expect($org->notification_settings->count())->toBe(1); + + // clean up + artisan('migrate:fresh'); +}); diff --git a/tests/Feature/EmailVerificationTest.php b/tests/Feature/EmailVerificationTest.php index 6843368db..0cc5061e3 100644 --- a/tests/Feature/EmailVerificationTest.php +++ b/tests/Feature/EmailVerificationTest.php @@ -1,5 +1,6 @@ create([ - 'context' => 'organization', + 'context' => UserContext::Organization->value, 'email_verified_at' => null, ]); @@ -39,7 +40,7 @@ test('email is not verified with invalid hash', function () { $user = User::factory()->create([ - 'context' => 'regulated-organization', + 'context' => UserContext::RegulatedOrganization->value, 'email_verified_at' => null, ]); diff --git a/tests/Feature/EngagementContractorsAndParticipantsTest.php b/tests/Feature/EngagementContractorsAndParticipantsTest.php index 15174b5a6..52b21f88f 100644 --- a/tests/Feature/EngagementContractorsAndParticipantsTest.php +++ b/tests/Feature/EngagementContractorsAndParticipantsTest.php @@ -1,5 +1,6 @@ project = $this->engagement->project; $this->project->update(['estimate_requested_at' => now(), 'agreement_received_at' => now()]); $this->regulatedOrganization = $this->project->projectable; - $this->regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); + $this->regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $this->regulatedOrganization->users()->attach( $this->regulatedOrganizationUser, ['role' => 'admin'] @@ -47,7 +48,7 @@ $this->individualConnector = $this->connectorUser->individual->fresh(); $this->connectorOrganization = Organization::factory()->create(['roles' => ['connector'], 'published_at' => now(), 'region' => 'AB', 'locality' => 'Medicine Hat']); - $this->connectorOrganizationUser = User::factory()->create(['context' => 'organization']); + $this->connectorOrganizationUser = User::factory()->create(['context' => UserContext::Organization->value]); $this->connectorOrganization->users()->attach( $this->connectorOrganizationUser, ['role' => 'admin'] @@ -59,7 +60,7 @@ $this->participant = $this->participantUser->individual->refresh(); $this->participantOrganization = Organization::factory()->create(['roles' => ['participant'], 'published_at' => now(), 'region' => 'AB', 'locality' => 'Medicine Hat']); - $this->participantOrganizationUser = User::factory()->create(['context' => 'organization']); + $this->participantOrganizationUser = User::factory()->create(['context' => UserContext::Organization->value]); $this->participantOrganization->users()->attach( $this->participantOrganizationUser, ['role' => 'admin'] @@ -218,7 +219,7 @@ }); test('user cannot be invited if they do not have the individual context', function () { - $user = User::factory()->create(['context' => 'organization']); + $user = User::factory()->create(['context' => UserContext::Organization->value]); $this->engagement->update(['individual_connector_id' => $this->individualConnector->id]); $this->engagement = $this->engagement->fresh(); @@ -629,7 +630,7 @@ $admin = User::factory()->create([ 'email_verified_at' => now(), - 'context' => 'administrator', + 'context' => UserContext::Administrator->value, ]); $this->engagement->update(['recruitment' => 'open-call']); @@ -768,7 +769,7 @@ function (JoinedEngagement $notification, $channels) { $admin = User::factory()->create([ 'email_verified_at' => now(), - 'context' => 'administrator', + 'context' => UserContext::Administrator->value, ]); $this->engagement->update(['recruitment' => 'open-call']); @@ -875,7 +876,7 @@ function (JoinedEngagement $notification, $channels) { $admin = User::factory()->create([ 'email_verified_at' => now(), - 'context' => 'administrator', + 'context' => UserContext::Administrator->value, ]); $this->engagement->update(['recruitment' => 'open-call']); @@ -943,7 +944,7 @@ function (JoinedEngagement $notification, $channels) { $admin = User::factory()->create([ 'email_verified_at' => now(), - 'context' => 'administrator', + 'context' => UserContext::Administrator->value, ]); $this->engagement->update(['recruitment' => 'open-call']); @@ -1023,7 +1024,7 @@ function (AccessNeedsFacilitationRequested $notification, $channels) { $admin = User::factory()->create([ 'email_verified_at' => now(), - 'context' => 'administrator', + 'context' => UserContext::Administrator->value, ]); $this->engagement->update(['recruitment' => 'open-call']); diff --git a/tests/Feature/EngagementTest.php b/tests/Feature/EngagementTest.php index 2d187551f..60dd8cea8 100644 --- a/tests/Feature/EngagementTest.php +++ b/tests/Feature/EngagementTest.php @@ -9,8 +9,10 @@ use App\Enums\IndividualRole; use App\Enums\LocationType; use App\Enums\MeetingType; +use App\Enums\OrganizationRole; use App\Enums\ProjectInitiator; use App\Enums\SeekingForEngagement; +use App\Enums\TeamRole; use App\Enums\UserContext; use App\Http\Requests\StoreEngagementRequest; use App\Http\Requests\UpdateEngagementRequest; @@ -27,17 +29,19 @@ use App\Models\RegulatedOrganization; use App\Models\Sector; use App\Models\User; +use App\Notifications\EngagementAdded; use App\Statuses\EngagementStatus; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Notification; use function Pest\Laravel\actingAs; use function Pest\Laravel\get; use function Pest\Laravel\withSession; test('users with regulated organization admin role can create engagements', function () { - $user = User::factory()->create(); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -134,10 +138,10 @@ }); test('users without regulated organization admin role cannot create engagements', function () { - $user = User::factory()->create(); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $other_user = User::factory()->create(); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'member']) + ->hasAttached($user, ['role' => TeamRole::Member->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -153,7 +157,7 @@ test('store engagement languages request validation errors', function (array $state, array $errors) { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -166,7 +170,7 @@ test('store engagement request validation errors', function (array $state, array $errors) { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -183,7 +187,7 @@ test('store engagement format request validation errors', function (array $state, array $errors) { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -199,7 +203,7 @@ test('store engagement recruitment request validation errors', function (array $state, array $errors) { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -212,6 +216,272 @@ ->assertSessionHasErrors($errors); })->with('storeEngagementRecruitmentRequestValidationErrors'); +test('notifications for Individual users are sent when new open-call engagements are published', function () { + Notification::fake(); + + $userWithNotifications = User::factory()->create([ + 'context' => UserContext::Individual->value, + 'notification_settings' => ['engagements' => '1'], + ]); + $userWithoutNotifications = User::factory()->create([ + 'context' => UserContext::Individual->value, + 'notification_settings' => ['engagements' => '0'], + ]); + + $user = User::factory()->create(['context' => UserContext::Organization->value]); + $organization = Organization::factory() + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) + ->create(); + + $project = Project::factory()->for($organization, 'projectable')->create([ + 'estimate_requested_at' => now(), + 'estimate_returned_at' => now(), + 'estimate_approved_at' => now(), + 'agreement_received_at' => now(), + ]); + + $engagement = Engagement::factory()->for($project)->create([ + 'published_at' => null, + ]); + + $engagement->meetings()->save(Meeting::factory()->create()); + + $data = UpdateEngagementRequest::factory()->meetingInPerson()->create([ + 'name' => ['en' => $engagement->name], + 'publish' => '1', + ]); + + actingAs($user)->put(localized_route('engagements.update', $engagement), $data) + ->assertSessionHasNoErrors() + ->assertRedirect(localized_route('engagements.manage', $engagement)); + + Notification::assertSentTo( + $userWithNotifications, + function (EngagementAdded $notification) use ($engagement, $organization) { + expect($notification->toMail()->subject)->toBe(__('New Engagement from :projectable', ['projectable' => $organization->getTranslation('name', locale())])); + $this->assertStringContainsString('A new engagement has been uploaded on The Accessibility Exchange:', $notification->toMail()->render()); + expect($notification->toArray()['engagement_id'])->toEqual($notification->engagement->id); + + return $notification->engagement->id === $engagement->id; + } + ); + + Notification::assertNotSentTo($userWithoutNotifications, EngagementAdded::class); +}); + +test('view notifications for Individual users about new open-call engagements', function () { + $userWithNotifications = User::factory()->create([ + 'context' => UserContext::Individual->value, + 'notification_settings' => ['engagements' => '1'], + ]); + $userWithoutNotifications = User::factory()->create([ + 'context' => UserContext::Individual->value, + 'notification_settings' => ['engagements' => '0'], + ]); + + $user = User::factory()->create(['context' => UserContext::Organization->value]); + $organization = Organization::factory() + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) + ->create(); + + $project = Project::factory()->for($organization, 'projectable')->create([ + 'estimate_requested_at' => now(), + 'estimate_returned_at' => now(), + 'estimate_approved_at' => now(), + 'agreement_received_at' => now(), + ]); + + $engagement = Engagement::factory()->for($project)->create([ + 'published_at' => null, + ]); + + $engagement->meetings()->save(Meeting::factory()->create()); + + $data = UpdateEngagementRequest::factory()->meetingInPerson()->create([ + 'name' => ['en' => $engagement->name], + 'publish' => '1', + ]); + + actingAs($user)->put(localized_route('engagements.update', $engagement), $data) + ->assertSessionHasNoErrors() + ->assertRedirect(localized_route('engagements.manage', $engagement)); + + actingAs($userWithNotifications)->get(localized_route('dashboard.notifications')) + ->assertOk() + ->assertSee(__('New engagement added')); + + actingAs($userWithoutNotifications)->get(localized_route('dashboard.notifications')) + ->assertOk() + ->assertDontSee(__('New engagement added')); +}); + +test('notifications are not sent for Individual users when an non-open-call engagement is published', function () { + Notification::fake(); + + $userWithNotifications = User::factory()->create([ + 'context' => UserContext::Individual->value, + 'notification_settings' => ['engagements' => '1'], + ]); + $userWithoutNotifications = User::factory()->create([ + 'context' => UserContext::Individual->value, + 'notification_settings' => ['engagements' => '0'], + ]); + + $user = User::factory()->create(['context' => UserContext::Organization->value]); + $organization = Organization::factory() + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) + ->create(); + + $project = Project::factory()->for($organization, 'projectable')->create([ + 'estimate_requested_at' => now(), + 'estimate_returned_at' => now(), + 'estimate_approved_at' => now(), + 'agreement_received_at' => now(), + ]); + + $engagement = Engagement::factory()->for($project)->create([ + 'published_at' => null, + 'recruitment' => EngagementRecruitment::CommunityConnector->value, + ]); + + $engagement->meetings()->save(Meeting::factory()->create()); + + $data = UpdateEngagementRequest::factory()->meetingInPerson()->create([ + 'name' => ['en' => $engagement->name], + 'publish' => '1', + ]); + + actingAs($user)->put(localized_route('engagements.update', $engagement), $data) + ->assertSessionHasNoErrors() + ->assertRedirect(localized_route('engagements.manage', $engagement)); + + Notification::assertNotSentTo($userWithNotifications, EngagementAdded::class); + Notification::assertNotSentTo($userWithoutNotifications, EngagementAdded::class); +}); + +test('notifications are sent for community org users when engagements are published', function () { + Notification::fake(); + + $orgWithNotifications = Organization::factory() + ->hasAttached( + User::factory()->state(['context' => UserContext::Organization->value]), + ['role' => TeamRole::Administrator->value]) + ->create(['notification_settings' => ['engagements' => '1']]); + + $orgWithoutNotifications = Organization::factory() + ->hasAttached( + User::factory()->state(['context' => UserContext::Organization->value]), + ['role' => TeamRole::Administrator->value] + ) + ->create(['notification_settings' => ['engagements' => '0']]); + + $user = User::factory()->create(['context' => UserContext::Organization->value]); + $organization = Organization::factory() + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) + ->create(); + + $project = Project::factory()->for($organization, 'projectable')->create([ + 'estimate_requested_at' => now(), + 'estimate_returned_at' => now(), + 'estimate_approved_at' => now(), + 'agreement_received_at' => now(), + ]); + + $engagement = Engagement::factory()->for($project)->create([ + 'published_at' => null, + ]); + + $engagement->meetings()->save(Meeting::factory()->create()); + + $data = UpdateEngagementRequest::factory()->meetingInPerson()->create([ + 'name' => ['en' => $engagement->name], + 'publish' => '1', + ]); + + actingAs($user)->put(localized_route('engagements.update', $engagement), $data) + ->assertSessionHasNoErrors() + ->assertRedirect(localized_route('engagements.manage', $engagement)); + + Notification::assertSentTo( + $orgWithNotifications, + function (EngagementAdded $notification) use ($engagement, $organization) { + expect($notification->toMail()->subject)->toBe(__('New Engagement from :projectable', ['projectable' => $organization->getTranslation('name', locale())])); + $this->assertStringContainsString('A new engagement has been uploaded on The Accessibility Exchange:', $notification->toMail()->render()); + expect($notification->toArray()['engagement_id'])->toEqual($notification->engagement->id); + + return $notification->engagement->id === $engagement->id; + + return true; + } + ); + + Notification::assertNotSentTo($orgWithoutNotifications, EngagementAdded::class); + Notification::assertNotSentTo($organization, EngagementAdded::class); +}); + +test('view notifications for community org users about new engagements', function () { + $userWithNotifications = User::factory()->create(['context' => UserContext::Organization->value]); + Organization::factory() + ->hasAttached($userWithNotifications, ['role' => TeamRole::Administrator->value]) + ->create([ + 'notification_settings' => ['engagements' => '1'], + 'roles' => [OrganizationRole::ConsultationParticipant->value], + ]); + + $userWithoutNotifications = User::factory()->create(['context' => UserContext::Organization->value]); + Organization::factory() + ->hasAttached($userWithoutNotifications, ['role' => TeamRole::Administrator->value]) + ->create([ + 'notification_settings' => ['engagements' => '0'], + 'roles' => [OrganizationRole::ConsultationParticipant->value], + ]); + + $user = User::factory()->create(['context' => UserContext::Organization->value]); + $organization = Organization::factory() + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) + ->create([ + 'roles' => [ + OrganizationRole::ConsultationParticipant->value, + OrganizationRole::CommunityConnector->value, + OrganizationRole::AccessibilityConsultant->value, + ], + ]); + + $project = Project::factory()->for($organization, 'projectable')->create([ + 'estimate_requested_at' => now(), + 'estimate_returned_at' => now(), + 'estimate_approved_at' => now(), + 'agreement_received_at' => now(), + ]); + + $engagement = Engagement::factory()->for($project)->create([ + 'published_at' => null, + ]); + + $engagement->meetings()->save(Meeting::factory()->create()); + + $data = UpdateEngagementRequest::factory()->meetingInPerson()->create([ + 'name' => ['en' => $engagement->name], + 'publish' => '1', + ]); + + actingAs($user)->put(localized_route('engagements.update', $engagement), $data) + ->assertSessionHasNoErrors() + ->assertRedirect(localized_route('engagements.manage', $engagement)); + + actingAs($userWithNotifications)->get(localized_route('dashboard.notifications')) + ->assertOk() + ->assertSee(__('New engagement added')); + + actingAs($userWithoutNotifications)->get(localized_route('dashboard.notifications')) + ->assertOk() + ->assertDontSee(__('New engagement added')); + + actingAs($user)->get(localized_route('dashboard.notifications')) + ->assertOk() + ->assertDontSee(__('New engagement added')); +}); + test('users can view engagements', function () { $user = User::factory()->create(); $engagement = Engagement::factory()->create(); @@ -254,7 +524,7 @@ test('users with regulated organization admin role can edit engagements', function () { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -522,7 +792,7 @@ $user = User::factory()->create(); $other_user = User::factory()->create(); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'member']) + ->hasAttached($user, ['role' => TeamRole::Member->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -549,7 +819,7 @@ test('update engagement request validation errors', function (array $state, array $errors, array $modifiers = []) { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -592,7 +862,7 @@ test('update engagement languages request validation errors', function (array $state, array $errors) { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -608,7 +878,7 @@ test('update engagement selection criteria request validation errors', function (array $state, array $errors, array $without = []) { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -627,7 +897,7 @@ test('users with regulated organization admin role can manage engagements', function () { $user = User::factory()->create(); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -646,7 +916,7 @@ ]); $other_user = User::factory()->create(); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'member']) + ->hasAttached($user, ['role' => TeamRole::Member->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -690,7 +960,7 @@ $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); // Fill data so that we don't hit a Database Integrity constraint violation during creation @@ -747,7 +1017,7 @@ $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); $individualUser = Individual::factory()->create()->user; $adminUser = User::factory()->create(['context' => UserContext::Administrator->value]); @@ -807,7 +1077,7 @@ $connectorOrganizationUser = User::factory()->create(['context' => UserContext::Organization->value]); $connectorOrganization->users()->attach( $connectorOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); $engagement = Engagement::factory()->create(['recruitment' => 'connector']); @@ -817,7 +1087,7 @@ $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); actingAs($user)->get(localized_route('engagements.manage-participants', $engagement)) @@ -862,7 +1132,7 @@ $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); $paymentType = PaymentType::factory()->create(['name' => __('Cash')]); @@ -896,7 +1166,7 @@ $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); $paymentType = PaymentType::factory()->create(); @@ -971,7 +1241,7 @@ $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); $otherAccessNeed = 'custom access need'; @@ -1001,7 +1271,7 @@ $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); actingAs($regulatedOrganizationUser) @@ -1020,7 +1290,7 @@ ]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, - ['role' => 'admin'] + ['role' => TeamRole::Administrator->value] ); $user = User::factory() @@ -1066,7 +1336,7 @@ test('project can show upcoming engagements', function () { $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(); $project = Project::factory()->create([ 'projectable_id' => $regulatedOrganization->id, @@ -1711,7 +1981,7 @@ test('Engagements I’ve joined pages for Organizations', function ($roles, $routes, $engagements, $engagementRoutes) { $user = User::factory()->create(['context' => UserContext::Organization->value]); $organization = Organization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(['roles' => $roles]); if (array_key_exists('connector', $engagements)) { @@ -1875,7 +2145,7 @@ test('Engagements I’ve joined engagement lists for Organizations', function ($roles, $routes, $engagementStates) { $user = User::factory()->create(['context' => UserContext::Organization->value]); $organization = Organization::factory() - ->hasAttached($user, ['role' => 'admin']) + ->hasAttached($user, ['role' => TeamRole::Administrator->value]) ->create(['roles' => $roles]); $engagements = []; diff --git a/tests/Feature/Filament/AccessSupportTest.php b/tests/Feature/Filament/AccessSupportTest.php index 9371099a2..f07c6ae3b 100644 --- a/tests/Feature/Filament/AccessSupportTest.php +++ b/tests/Feature/Filament/AccessSupportTest.php @@ -1,5 +1,6 @@ admin = User::factory()->create(['context' => 'administrator']); + $this->admin = User::factory()->create(['context' => UserContext::Administrator->value]); }); test('only administrative users can access access support admin pages', function () { diff --git a/tests/Feature/Filament/ContentTypeTest.php b/tests/Feature/Filament/ContentTypeTest.php index bd02c4220..c07c1be5b 100644 --- a/tests/Feature/Filament/ContentTypeTest.php +++ b/tests/Feature/Filament/ContentTypeTest.php @@ -1,5 +1,6 @@ admin = User::factory()->create(['context' => 'administrator']); + $this->admin = User::factory()->create(['context' => UserContext::Administrator->value]); }); test('only administrative users can access content type admin pages', function () { diff --git a/tests/Feature/Filament/IdentityTest.php b/tests/Feature/Filament/IdentityTest.php index 965d6f6ec..ef3c10950 100644 --- a/tests/Feature/Filament/IdentityTest.php +++ b/tests/Feature/Filament/IdentityTest.php @@ -1,5 +1,6 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $identity = Identity::factory()->create(); actingAs($user)->get(IdentityResource::getUrl('index'))->assertForbidden(); @@ -21,7 +22,7 @@ }); test('identities can be listed', function () { - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); actingAs($administrator); diff --git a/tests/Feature/Filament/ImpactTest.php b/tests/Feature/Filament/ImpactTest.php index 293eb6a2c..7f8310fff 100644 --- a/tests/Feature/Filament/ImpactTest.php +++ b/tests/Feature/Filament/ImpactTest.php @@ -1,5 +1,6 @@ admin = User::factory()->create(['context' => 'administrator']); + $this->admin = User::factory()->create(['context' => UserContext::Administrator->value]); }); test('only administrative users can access impact admin pages', function () { diff --git a/tests/Feature/Filament/InterpretationTest.php b/tests/Feature/Filament/InterpretationTest.php index a4a05f813..feb5a958a 100644 --- a/tests/Feature/Filament/InterpretationTest.php +++ b/tests/Feature/Filament/InterpretationTest.php @@ -1,5 +1,6 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); actingAs($user)->get(InterpretationResource::getUrl('index'))->assertForbidden(); actingAs($administrator)->get(InterpretationResource::getUrl('index'))->assertSuccessful(); diff --git a/tests/Feature/Filament/LanguageTest.php b/tests/Feature/Filament/LanguageTest.php index 272f65305..56f7d2e81 100644 --- a/tests/Feature/Filament/LanguageTest.php +++ b/tests/Feature/Filament/LanguageTest.php @@ -1,5 +1,6 @@ admin = User::factory()->create(['context' => 'administrator']); + $this->admin = User::factory()->create(['context' => UserContext::Administrator->value]); }); test('only administrative users can access language admin pages', function () { diff --git a/tests/Feature/Filament/PageTest.php b/tests/Feature/Filament/PageTest.php index 595ed184a..cb5a7b08d 100644 --- a/tests/Feature/Filament/PageTest.php +++ b/tests/Feature/Filament/PageTest.php @@ -1,5 +1,6 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $page = Page::factory()->create(); actingAs($user)->get(PageResource::getUrl('index'))->assertForbidden(); diff --git a/tests/Feature/Filament/PaymentTypeTest.php b/tests/Feature/Filament/PaymentTypeTest.php index 0ae1ff0bb..720c45f61 100644 --- a/tests/Feature/Filament/PaymentTypeTest.php +++ b/tests/Feature/Filament/PaymentTypeTest.php @@ -1,5 +1,6 @@ admin = User::factory()->create(['context' => 'administrator']); + $this->admin = User::factory()->create(['context' => UserContext::Administrator->value]); }); test('only administrative users can access payment type admin pages', function () { diff --git a/tests/Feature/Filament/ResourceCollectionTest.php b/tests/Feature/Filament/ResourceCollectionTest.php index d12cce0a4..680075c8e 100644 --- a/tests/Feature/Filament/ResourceCollectionTest.php +++ b/tests/Feature/Filament/ResourceCollectionTest.php @@ -1,5 +1,6 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $resourceCollection = ResourceCollection::factory()->create(); actingAs($user)->get(ResourceCollectionResource::getUrl('index'))->assertForbidden(); diff --git a/tests/Feature/Filament/ResourceTest.php b/tests/Feature/Filament/ResourceTest.php index ea991d43d..8d5750255 100644 --- a/tests/Feature/Filament/ResourceTest.php +++ b/tests/Feature/Filament/ResourceTest.php @@ -1,5 +1,6 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); actingAs($user)->get(ResourceResource::getUrl('index'))->assertForbidden(); actingAs($administrator)->get(ResourceResource::getUrl('index'))->assertSuccessful(); diff --git a/tests/Feature/Filament/SectorTest.php b/tests/Feature/Filament/SectorTest.php index 7b3010bfd..1c042280a 100644 --- a/tests/Feature/Filament/SectorTest.php +++ b/tests/Feature/Filament/SectorTest.php @@ -1,5 +1,6 @@ admin = User::factory()->create(['context' => 'administrator']); + $this->admin = User::factory()->create(['context' => UserContext::Administrator->value]); }); test('only administrative users can access sector admin pages', function () { diff --git a/tests/Feature/Filament/SettingsTest.php b/tests/Feature/Filament/SettingsTest.php index 462b6130d..bbe9e141c 100644 --- a/tests/Feature/Filament/SettingsTest.php +++ b/tests/Feature/Filament/SettingsTest.php @@ -1,12 +1,13 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); actingAs($user)->get(route('filament.admin.pages.settings')) ->assertForbidden(); diff --git a/tests/Feature/Filament/TopicTest.php b/tests/Feature/Filament/TopicTest.php index ab6a993a7..f0bdd71df 100644 --- a/tests/Feature/Filament/TopicTest.php +++ b/tests/Feature/Filament/TopicTest.php @@ -1,5 +1,6 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); actingAs($user)->get(TopicResource::getUrl('index'))->assertForbidden(); actingAs($administrator)->get(TopicResource::getUrl('index'))->assertSuccessful(); diff --git a/tests/Feature/IndividualTest.php b/tests/Feature/IndividualTest.php index 14c8ce26e..d0ba5d9c9 100644 --- a/tests/Feature/IndividualTest.php +++ b/tests/Feature/IndividualTest.php @@ -34,13 +34,6 @@ use function Pest\Laravel\seed; use function Pest\Laravel\withSession; -beforeEach(function () { - seed(IdentitySeeder::class); - - $this->livedExperience = Identity::withoutGlobalScope(ReachableIdentityScope::class)->whereJsonContains('clusters', IdentityCluster::LivedExperience)->first(); - $this->areaType = Identity::whereJsonContains('clusters', IdentityCluster::Area)->first(); -}); - test('individual users can select an individual role', function () { $user = User::factory()->create(); @@ -207,6 +200,15 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, seed(ImpactSeeder::class); seed(SectorSeeder::class); + $livedExperience = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::LivedExperience->value], + ]); + $areaType = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::Area->value], + ]); + withSession([ 'locale' => 'en', 'name' => 'Test User', @@ -234,8 +236,8 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, ]); $individual->save(); - $individual->identityConnections()->attach($this->livedExperience->id); - $individual->identityConnections()->attach($this->areaType->id); + $individual->identityConnections()->attach($livedExperience->id); + $individual->identityConnections()->attach($areaType->id); expect($individual)->toBeInstanceOf(Individual::class); @@ -524,7 +526,7 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, })->with('updateIndividualCommunicationAndConsultationPreferencesRequestValidationErrors'); test('entity users can not create individual pages', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); expect($user->individual)->toBeNull(); }); @@ -534,16 +536,25 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, $individual->roles = [IndividualRole::CommunityConnector->value]; $individual->save(); + $livedExperience = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::LivedExperience->value], + ]); + $areaType = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::Area->value], + ]); + expect($individual->base_disability_type)->toEqual(''); expect($individual->hasConnections('disabilityAndDeafConnections'))->toBeNull(); actingAs($user)->put(localized_route('individuals.update-constituencies', $individual), [])->assertSessionHasErrors(); - $disabilityOrDeafIdentity = Identity::whereJsonContains('clusters', IdentityCluster::DisabilityAndDeaf)->first(); + $disabilityOrDeafIdentity = Identity::factory()->create(['clusters' => [IdentityCluster::DisabilityAndDeaf->value]]); $data = UpdateIndividualConstituenciesRequest::factory()->create([ - 'lived_experience_connections' => [$this->livedExperience->id], - 'area_type_connections' => [$this->areaType->id], + 'lived_experience_connections' => [$livedExperience->id], + 'area_type_connections' => [$areaType->id], 'disability_and_deaf_connections' => [$disabilityOrDeafIdentity->id], ]); @@ -556,14 +567,14 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, expect($individual->hasConnections('genderDiverseConnections'))->toBeFalse(); expect($individual->hasConnections('disabilityAndDeafConnections'))->toBeTrue(); expect($individual->disabilityAndDeafConnections)->toHaveCount(1); - expect($this->livedExperience->communityConnectors)->toHaveCount(1); + expect($livedExperience->communityConnectors)->toHaveCount(1); expect($individual->other_disability_connection)->toEqual('Something not listed'); $data = UpdateIndividualConstituenciesRequest::factory()->create([ - 'lived_experience_connections' => [$this->livedExperience->id], + 'lived_experience_connections' => [$livedExperience->id], 'disability_and_deaf' => false, 'base_disability_type' => null, - 'area_type_connections' => [$this->areaType->id], + 'area_type_connections' => [$areaType->id], 'has_other_disability_connection' => null, ]); @@ -581,10 +592,19 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, $individual->roles = [IndividualRole::CommunityConnector->value]; $individual->save(); + $livedExperience = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::LivedExperience->value], + ]); + $areaType = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::Area->value], + ]); + $data = UpdateIndividualConstituenciesRequest::factory()->create([ - 'lived_experience_connections' => [$this->livedExperience->id], + 'lived_experience_connections' => [$livedExperience->id], 'base_disability_type' => 'cross_disability_and_deaf', - 'area_type_connections' => [$this->areaType->id], + 'area_type_connections' => [$areaType->id], ]); actingAs($user)->put(localized_route('individuals.update-constituencies', $individual), $data)->assertSessionHasNoErrors(); @@ -594,10 +614,10 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, expect($individual->base_disability_type)->toEqual('cross_disability_and_deaf'); $data = UpdateIndividualConstituenciesRequest::factory()->create([ - 'lived_experience_connections' => [$this->livedExperience->id], + 'lived_experience_connections' => [$livedExperience->id], 'disability_and_deaf' => false, 'base_disability_type' => null, - 'area_type_connections' => [$this->areaType->id], + 'area_type_connections' => [$areaType->id], ]); actingAs($user)->put(localized_route('individuals.update-constituencies', $individual), $data); @@ -613,11 +633,20 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, $individual->roles = [IndividualRole::CommunityConnector->value]; $individual->save(); - $ageBracket = Identity::whereJsonContains('clusters', IdentityCluster::Age)->first(); + $livedExperience = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::LivedExperience->value], + ]); + $areaType = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::Area->value], + ]); + + $ageBracket = Identity::factory()->create(['clusters' => [IdentityCluster::Age->value]]); $data = UpdateIndividualConstituenciesRequest::factory()->create([ - 'lived_experience_connections' => [$this->livedExperience->id], - 'area_type_connections' => [$this->areaType->id], + 'lived_experience_connections' => [$livedExperience->id], + 'area_type_connections' => [$areaType->id], 'has_age_bracket_connections' => 1, 'age_bracket_connections' => [$ageBracket->id], ]); @@ -631,14 +660,19 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, }); test('individuals with connector role can represent refugees and immigrants', function () { + seed(IdentitySeeder::class); + + $livedExperience = Identity::withoutGlobalScope(ReachableIdentityScope::class)->whereJsonContains('clusters', IdentityCluster::LivedExperience)->first(); + $areaType = Identity::whereJsonContains('clusters', IdentityCluster::Area)->first(); + $user = User::factory()->create(); $individual = $user->individual; $individual->roles = [IndividualRole::CommunityConnector->value]; $individual->save(); $data = UpdateIndividualConstituenciesRequest::factory()->create([ - 'lived_experience_connections' => [$this->livedExperience->id], - 'area_type_connections' => [$this->areaType->id], + 'lived_experience_connections' => [$livedExperience->id], + 'area_type_connections' => [$areaType->id], 'refugees_and_immigrants' => 1, ]); @@ -650,6 +684,11 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, }); test('individuals with connector role can represent gender and sexual minorities', function () { + seed(IdentitySeeder::class); + + $livedExperience = Identity::withoutGlobalScope(ReachableIdentityScope::class)->whereJsonContains('clusters', IdentityCluster::LivedExperience)->first(); + $areaType = Identity::whereJsonContains('clusters', IdentityCluster::Area)->first(); + $user = User::factory()->create(); $individual = $user->individual; $individual->roles = [IndividualRole::CommunityConnector->value]; @@ -663,8 +702,8 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, })->pluck('id')->toArray()); $data = UpdateIndividualConstituenciesRequest::factory()->create([ - 'lived_experience_connections' => [$this->livedExperience->id], - 'area_type_connections' => [$this->areaType->id], + 'lived_experience_connections' => [$livedExperience->id], + 'area_type_connections' => [$areaType->id], 'has_gender_and_sexuality_connections' => 1, 'nb_gnc_fluid_identity' => 1, 'gender_and_sexuality_connections' => $genderAndSexualIdentities, @@ -685,12 +724,20 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, $individual->roles = [IndividualRole::CommunityConnector->value]; $individual->save(); - $ethnoracialIdentity = Identity::whereJsonContains('clusters', IdentityCluster::Ethnoracial)->first(); + $livedExperience = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::LivedExperience->value], + ]); + $areaType = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::Area->value], + ]); + $ethnoracialIdentity = Identity::factory()->create(['clusters' => [IdentityCluster::Ethnoracial->value]]); $data = UpdateIndividualConstituenciesRequest::factory()->create([ - 'lived_experience_connections' => [$this->livedExperience->id], + 'lived_experience_connections' => [$livedExperience->id], 'ethnoracial_identity_connections' => [$ethnoracialIdentity->id], - 'area_type_connections' => [$this->areaType->id], + 'area_type_connections' => [$areaType->id], ]); unset($data['has_other_ethnoracial_identity_connection']); @@ -1119,12 +1166,16 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, $individual->update($data); $individual = $individual->fresh(); - $indigenousIdentity = Identity::whereJsonContains('clusters', IdentityCluster::Indigenous)->first(); - $ageBracket = Identity::whereJsonContains('clusters', IdentityCluster::Age)->first(); + $areaType = Identity::factory()->create([ + 'description' => null, + 'clusters' => [IdentityCluster::Area->value], + ]); + $indigenousIdentity = Identity::factory()->create(['clusters' => [IdentityCluster::Indigenous->value]]); + $ageBracket = Identity::factory()->create(['clusters' => [IdentityCluster::Age->value]]); foreach ($connections as $connection) { if ($connection === 'areaTypeConnections') { - $individual->areaTypeConnections()->attach($this->areaType->id); + $individual->areaTypeConnections()->attach($areaType->id); } if ($connection === 'indigenousConnections') { @@ -1223,7 +1274,7 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, $user = User::factory()->create(); $individual = $user->individual; - $disabilityOrDeafIdentity = Identity::whereJsonContains('clusters', IdentityCluster::DisabilityAndDeaf)->first(); + $disabilityOrDeafIdentity = Identity::factory()->create(['clusters' => [IdentityCluster::DisabilityAndDeaf->value]]); $individual->identities()->sync([$disabilityOrDeafIdentity->id]); $individual->refresh(); @@ -1303,7 +1354,7 @@ function (IndividualPublicPageNeedsUpdate $notification, $channels) use ($user, ->create($data); if ($withIdentity) { - $individual->identityConnections()->attach(Identity::first()); + $individual->identityConnections()->attach(Identity::factory()->create(['clusters' => [IdentityCluster::Age->value]])); } expect($individual->isInProgress())->toEqual($expected); diff --git a/tests/Feature/Livewire/AddEngagementConnectorTest.php b/tests/Feature/Livewire/AddEngagementConnectorTest.php index f3fe9cfef..e87331e94 100644 --- a/tests/Feature/Livewire/AddEngagementConnectorTest.php +++ b/tests/Feature/Livewire/AddEngagementConnectorTest.php @@ -1,6 +1,7 @@ project->projectable; - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $user, @@ -58,7 +59,7 @@ $regulatedOrganization = $engagement->project->projectable; - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $user, @@ -132,7 +133,7 @@ $regulatedOrganization = $engagement->project->projectable; - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $user, @@ -141,7 +142,7 @@ $organization = Organization::factory()->create(['roles' => ['consultant'], 'published_at' => now(), 'region' => 'AB', 'locality' => 'Medicine Hat']); - $organizationUser = User::factory()->create(['context' => 'organization']); + $organizationUser = User::factory()->create(['context' => UserContext::Organization->value]); $organization->users()->attach( $organizationUser, @@ -232,7 +233,7 @@ $engagement = Engagement::factory()->create(['recruitment' => 'connector']); $areaIdentity = Identity::whereJsonContains('clusters', IdentityCluster::Area)->first(); $fro = $engagement->project->projectable; - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $fro->users()->attach( $user, ['role' => 'admin'] @@ -240,7 +241,7 @@ $orgNotOriented = Organization::factory() ->hasAttached( - User::factory()->state(['context' => 'organization']), + User::factory()->state(['context' => UserContext::Organization->value]), ['role' => 'admin'] ) ->create([ @@ -250,7 +251,7 @@ $orgNotPublishable = Organization::factory() ->hasAttached( - User::factory()->state(['context' => 'organization']), + User::factory()->state(['context' => UserContext::Organization->value]), ['role' => 'admin'] ) ->create([ @@ -259,7 +260,7 @@ $orgSuspended = Organization::factory() ->hasAttached( - User::factory()->state(['context' => 'organization']), + User::factory()->state(['context' => UserContext::Organization->value]), ['role' => 'admin'] ) ->create([ @@ -277,7 +278,7 @@ $organization = Organization::factory() ->hasAttached( - User::factory()->state(['context' => 'organization']), + User::factory()->state(['context' => UserContext::Organization->value]), ['role' => 'admin'] ) ->create([ diff --git a/tests/Feature/Livewire/EstimateApproverTest.php b/tests/Feature/Livewire/EstimateApproverTest.php index b6d052815..a3fca8424 100644 --- a/tests/Feature/Livewire/EstimateApproverTest.php +++ b/tests/Feature/Livewire/EstimateApproverTest.php @@ -1,5 +1,6 @@ create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $project = Project::factory()->create(); $regulatedOrganization = $project->projectable; - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $user, ['role' => 'admin'] diff --git a/tests/Feature/Livewire/EstimateRequesterTest.php b/tests/Feature/Livewire/EstimateRequesterTest.php index 4660f2ea5..b0de2dc2b 100644 --- a/tests/Feature/Livewire/EstimateRequesterTest.php +++ b/tests/Feature/Livewire/EstimateRequesterTest.php @@ -1,5 +1,6 @@ create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $project = Project::factory()->create(); $regulatedOrganization = $project->projectable; - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $user, ['role' => 'admin'] diff --git a/tests/Feature/Livewire/EstimatesAndAgreementsTest.php b/tests/Feature/Livewire/EstimatesAndAgreementsTest.php index 488976dc8..773676ca9 100644 --- a/tests/Feature/Livewire/EstimatesAndAgreementsTest.php +++ b/tests/Feature/Livewire/EstimatesAndAgreementsTest.php @@ -1,5 +1,6 @@ create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $project = Project::factory()->create([ 'estimate_requested_at' => now(), ]); - $projectManager = User::factory()->create(['context' => 'regulated-organization']); + $projectManager = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $project->projectable->users()->attach($projectManager, ['role' => 'admin']); actingAs($administrator); @@ -83,7 +84,7 @@ }); test('agreement can be marked as received', function () { - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $datetime = now(); @@ -93,7 +94,7 @@ 'estimate_approved_at' => $datetime, ]); - $projectManager = User::factory()->create(['context' => 'regulated-organization']); + $projectManager = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $project->projectable->users()->attach($projectManager, ['role' => 'admin']); actingAs($administrator); diff --git a/tests/Feature/Livewire/ManageAccountsTest.php b/tests/Feature/Livewire/ManageAccountsTest.php index 9dd9b67ae..b188bdd95 100644 --- a/tests/Feature/Livewire/ManageAccountsTest.php +++ b/tests/Feature/Livewire/ManageAccountsTest.php @@ -1,5 +1,6 @@ organizationUser = User::factory()->create(['context' => 'organization']); - $this->secondaryOrganizationUser = User::factory()->create(['context' => 'organization']); + $this->organizationUser = User::factory()->create(['context' => UserContext::Organization->value]); + $this->secondaryOrganizationUser = User::factory()->create(['context' => UserContext::Organization->value]); $this->organization = Organization::factory()->create([ 'oriented_at' => null, 'validated_at' => null, @@ -31,7 +32,7 @@ ['role' => 'admin'] ); - $this->organizationalParticipantUser = User::factory()->create(['context' => 'organization']); + $this->organizationalParticipantUser = User::factory()->create(['context' => UserContext::Organization->value]); $this->organizationalParticipant = Organization::factory()->create([ 'oriented_at' => null, 'validated_at' => null, @@ -43,8 +44,8 @@ ['role' => 'admin'] ); - $this->regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); - $this->secondaryRegulatedOrganizationUser = User::factory()->create(['context' => 'oregulated-rganization']); + $this->regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $this->secondaryRegulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $this->regulatedOrganization = RegulatedOrganization::factory()->create([ 'oriented_at' => null, 'validated_at' => null, diff --git a/tests/Feature/Livewire/ManageEngagementConnectorTest.php b/tests/Feature/Livewire/ManageEngagementConnectorTest.php index a6e267327..25d89faa4 100644 --- a/tests/Feature/Livewire/ManageEngagementConnectorTest.php +++ b/tests/Feature/Livewire/ManageEngagementConnectorTest.php @@ -1,5 +1,6 @@ project->projectable; - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $user, @@ -41,7 +42,7 @@ $project = $engagement->project; $project->update(['estimate_requested_at' => now(), 'agreement_received_at' => now()]); $regulatedOrganization = $project->projectable; - $regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, ['role' => 'admin'] @@ -77,7 +78,7 @@ $project = $engagement->project; $project->update(['estimate_requested_at' => now(), 'agreement_received_at' => now()]); $regulatedOrganization = $project->projectable; - $regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, ['role' => 'admin'] @@ -107,7 +108,7 @@ $project = $engagement->project; $project->update(['estimate_requested_at' => now(), 'agreement_received_at' => now()]); $regulatedOrganization = $project->projectable; - $regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach( $regulatedOrganizationUser, ['role' => 'admin'] diff --git a/tests/Feature/MatchingStrategyTest.php b/tests/Feature/MatchingStrategyTest.php index 5bf7ccde7..f3fffd53e 100644 --- a/tests/Feature/MatchingStrategyTest.php +++ b/tests/Feature/MatchingStrategyTest.php @@ -1,6 +1,7 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); expect($user->can('view', $this->strategy))->toBeFalse(); expect($administrator->can('view', $this->strategy))->toBeTrue(); diff --git a/tests/Feature/MeetingTest.php b/tests/Feature/MeetingTest.php index ea6bd4ddb..eaccef472 100644 --- a/tests/Feature/MeetingTest.php +++ b/tests/Feature/MeetingTest.php @@ -14,7 +14,7 @@ use function Pest\Laravel\seed; beforeEach(function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $otherUser = User::factory()->create(); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) diff --git a/tests/Feature/NotificationListTest.php b/tests/Feature/NotificationListTest.php index a3cb4f1f4..dcf19ce00 100644 --- a/tests/Feature/NotificationListTest.php +++ b/tests/Feature/NotificationListTest.php @@ -1,5 +1,6 @@ get(localized_route('notification-list.show')) ->assertOk(); - $regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); actingAs($regulatedOrganizationUser)->get(localized_route('notification-list.show')) ->assertForbidden(); diff --git a/tests/Feature/NotificationsTest.php b/tests/Feature/NotificationsTest.php index 0fe40f94e..89fbc4cf6 100644 --- a/tests/Feature/NotificationsTest.php +++ b/tests/Feature/NotificationsTest.php @@ -1,5 +1,6 @@ create(); - $organizationAdministrator = User::factory()->create(['context' => 'organization']); + $organizationAdministrator = User::factory()->create(['context' => UserContext::Organization->value]); $organization->users()->attach($organizationAdministrator, ['role' => 'admin']); $project->notify(new AgreementReceived($project)); @@ -68,7 +69,7 @@ 'projectable_id' => $regulatedOrganization->id, ]); - $regulatedOrganizationAdministrator = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationAdministrator = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization->users()->attach($regulatedOrganizationAdministrator, ['role' => 'admin']); $project->notify(new AgreementReceived($project)); diff --git a/tests/Feature/OrganizationTest.php b/tests/Feature/OrganizationTest.php index e9aa31030..5c434ed24 100644 --- a/tests/Feature/OrganizationTest.php +++ b/tests/Feature/OrganizationTest.php @@ -69,6 +69,7 @@ expect($organization->preferredLocale())->toBe('en'); expect($organization->working_languages)->toContain('asl'); + expect($organization->notification_settings->get('engagements'))->toBe('1'); actingAs($user)->get(localized_route('organizations.show-role-selection', $organization))->assertOk(); actingAs($user)->from(localized_route('organizations.show-role-selection', $organization))->put(localized_route('organizations.save-roles', $organization), [ @@ -1470,3 +1471,24 @@ function (OrganizationPageNeedsUpdate $notification, $channels) use ($organizati expect($organization->preferredLocale())->toBe('fr'); }); + +test('organizations can be found via schemaless scope', function () { + Organization::factory()->create([ + 'notification_settings' => ['engagements' => '0'], + 'extra_attributes' => ['disability_and_deaf_constituencies' => '1'], + ]); + Organization::factory()->create([ + 'notification_settings' => ['engagements' => '1'], + 'extra_attributes' => ['cross_disability_and_deaf_constituencies' => '1'], + ]); + + $withNotifications = Organization::withNotificationSettings('engagements', '1')->get(); + + expect($withNotifications)->toHaveCount(1); + expect($withNotifications->first()->notification_settings->get('engagements'))->toBe('1'); + + $withExtraAttributes = Organization::withExtraAttributes('disability_and_deaf_constituencies', '1')->get(); + + expect($withExtraAttributes)->toHaveCount(1); + expect($withExtraAttributes->first()->extra_attributes->get('disability_and_deaf_constituencies'))->toBe('1'); +}); diff --git a/tests/Feature/ProjectTest.php b/tests/Feature/ProjectTest.php index 5893f526b..4f5ca714e 100644 --- a/tests/Feature/ProjectTest.php +++ b/tests/Feature/ProjectTest.php @@ -266,7 +266,7 @@ })->with('projectIsPublishable'); test('organization or regulated organization users can not view projects, other than their owned project, if they are not oriented', function () { - $organizationUser = User::factory()->create(['context' => 'organization', 'oriented_at' => null]); + $organizationUser = User::factory()->create(['context' => UserContext::Organization->value, 'oriented_at' => null]); $organization = Organization::factory()->hasAttached($organizationUser, ['role' => 'admin'])->create(['oriented_at' => null]); $organizationUser->refresh(); @@ -281,7 +281,7 @@ actingAs($organizationUser)->get(localized_route('engagements.index'))->assertOk(); - $regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization', 'oriented_at' => null]); + $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value, 'oriented_at' => null]); $regulatedOrganization = RegulatedOrganization::factory()->hasAttached($regulatedOrganizationUser, ['role' => 'admin'])->create(['oriented_at' => null]); $regulatedOrganizationUser->refresh(); @@ -894,8 +894,7 @@ }); test('guests can not access my projects page', function () { - get(localized_route('projects.my-projects')) - ->assertRedirect(localized_route('login')); + get(localized_route('projects.my-projects'))->assertRedirect(localized_route('login')); }); test('my projects page displays projects by status', function ($userContext, $modelClass, $projectState, $toSee, $dontSee) { diff --git a/tests/Feature/RegistrationTest.php b/tests/Feature/RegistrationTest.php index f3ae7345a..bf6c2225f 100644 --- a/tests/Feature/RegistrationTest.php +++ b/tests/Feature/RegistrationTest.php @@ -1,5 +1,6 @@ 'en', ]) ->post(localized_route('register-context'), [ - 'context' => 'individual', + 'context' => UserContext::Individual->value, ]) ->assertRedirect(localized_route('register', ['step' => 3])) - ->assertSessionHas('context', 'individual') + ->assertSessionHas('context', UserContext::Individual->value) ->assertSessionHas('isNewOrganizationContext', false); from(localized_route('register', ['step' => 3])) ->withSession([ 'locale' => 'en', - 'context' => 'individual', + 'context' => UserContext::Individual->value, ]) ->post(localized_route('register-details'), [ 'name' => 'Test User', @@ -53,7 +54,7 @@ from(localized_route('register', ['step' => 3])) ->withSession([ 'locale' => 'en', - 'context' => 'individual', + 'context' => UserContext::Individual->value, ]) ->post(localized_route('register-details'), [ 'name' => 'Test User', @@ -67,7 +68,7 @@ 'locale' => 'en', 'name' => 'Test User', 'email' => 'test@example.com', - 'context' => 'individual', + 'context' => UserContext::Individual->value, ])->post(localized_route('register-store'), [ 'password' => 'correctHorse-batteryStaple7', 'password_confirmation' => 'correctHorse-batteryStaple7', @@ -93,16 +94,16 @@ 'locale' => 'en', ]) ->post(localized_route('register-context'), [ - 'context' => 'organization', + 'context' => UserContext::Organization->value, ]) ->assertRedirect(localized_route('register', ['step' => 3])) - ->assertSessionHas('context', 'organization') + ->assertSessionHas('context', UserContext::Organization->value) ->assertSessionHas('isNewOrganizationContext', true); from(localized_route('register', ['step' => 3])) ->withSession([ 'locale' => 'en', - 'context' => 'organization', + 'context' => UserContext::Organization->value, ]) ->post(localized_route('register-details'), [ 'name' => 'Test User', @@ -114,7 +115,7 @@ from(localized_route('register', ['step' => 3])) ->withSession([ 'locale' => 'en', - 'context' => 'organization', + 'context' => UserContext::Organization->value, ]) ->post(localized_route('register-details'), [ 'name' => 'Test User', @@ -128,7 +129,7 @@ 'locale' => 'en', 'name' => 'Test User', 'email' => 'test@example.com', - 'context' => 'organization', + 'context' => UserContext::Organization->value, ])->post(localized_route('register-store'), [ 'password' => 'correctHorse-batteryStaple7', 'password_confirmation' => 'correctHorse-batteryStaple7', @@ -157,7 +158,7 @@ ]); get(localized_route('register', [ - 'context' => 'regulated-organization', + 'context' => UserContext::RegulatedOrganization->value, 'invitation' => 1, 'email' => 'test@example.com', ])) @@ -167,11 +168,11 @@ post(localized_route('register-languages'), [ 'locale' => 'en', - 'context' => 'regulated-organization', + 'context' => UserContext::RegulatedOrganization->value, 'invitation' => 1, 'email' => 'test@example.com', ]) - ->assertSessionHas('context', 'regulated-organization') + ->assertSessionHas('context', UserContext::RegulatedOrganization->value) ->assertSessionHas('isNewOrganizationContext', false) ->assertSessionHas('invitation', '1') ->assertSessionHas('email', 'test@example.com'); @@ -180,7 +181,7 @@ 'locale' => 'en', 'name' => 'Test User', 'email' => 'test@example.com', - 'context' => 'regulated-organization', + 'context' => UserContext::RegulatedOrganization->value, 'invitation' => 1, ])->post(localized_route('register-store'), [ 'password' => 'correctHorse-batteryStaple7', @@ -214,7 +215,7 @@ ]); get(localized_route('register', [ - 'context' => 'individual', + 'context' => UserContext::Individual->value, 'role' => 'participant', 'invitation' => 1, 'email' => 'test@example.com', @@ -226,12 +227,12 @@ post(localized_route('register-languages'), [ 'locale' => 'en', - 'context' => 'individual', + 'context' => UserContext::Individual->value, 'invitation' => 1, 'role' => 'participant', 'email' => 'test@example.com', ]) - ->assertSessionHas('context', 'individual') + ->assertSessionHas('context', UserContext::Individual->value) ->assertSessionHas('invitation', '1') ->assertSessionHas('invited_role', 'participant') ->assertSessionHas('email', 'test@example.com'); @@ -240,7 +241,7 @@ 'locale' => 'en', 'name' => 'Test User', 'email' => 'test@example.com', - 'context' => 'individual', + 'context' => UserContext::Individual->value, 'invitation' => 1, 'invited_role' => 'participant', ])->post(localized_route('register-store'), [ diff --git a/tests/Feature/RegulatedOrganizationTest.php b/tests/Feature/RegulatedOrganizationTest.php index 343ac9d40..f515d28fc 100644 --- a/tests/Feature/RegulatedOrganizationTest.php +++ b/tests/Feature/RegulatedOrganizationTest.php @@ -1,6 +1,7 @@ create(); actingAs($individualUser)->get(localized_route('regulated-organizations.show-type-selection'))->assertForbidden(); - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); actingAs($user)->get(localized_route('regulated-organizations.show-type-selection'))->assertOk(); @@ -62,7 +63,7 @@ }); test('store regulated organization type request validation errors', function (array $state, array $errors) { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); actingAs($user) ->post(localized_route('regulated-organizations.store-type'), $state) @@ -70,7 +71,7 @@ })->with('storeRegulatedOrganizationTypeRequestValidationErrors'); test('store regulated organization request validation errors', function (array $state, array $errors, array $without = []) { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $data = StoreRegulatedOrganizationRequest::factory()->without($without ?? [])->create($state); @@ -80,7 +81,7 @@ })->with('storeRegulatedOrganizationRequestValidationErrors'); test('store regulated organization languages request validation errors', function (array $state, array $errors) { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); @@ -91,7 +92,7 @@ })->with('storeRegulatedOrganizationLanguagesRequestValidationErrors'); test('users primary entity can be retrieved', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); @@ -104,7 +105,7 @@ test('users with admin role can edit regulated organizations', function () { seed(); - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create([ @@ -193,7 +194,7 @@ }); test('users without admin role can not edit regulated organizations', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'member']) ->create(); @@ -209,8 +210,8 @@ }); test('non members can not edit regulated organizations', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $other_user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $other_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $otherRegulatedOrganization = RegulatedOrganization::factory() ->hasAttached($other_user, ['role' => 'admin']) @@ -228,7 +229,7 @@ test('update regulated organization request validation errors', function (array $state, array $errors, array $without = []) { seed(SectorSeeder::class); - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); @@ -242,7 +243,7 @@ test('regulated organizations can be published', function () { seed(SectorSeeder::class); - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create([ @@ -267,7 +268,7 @@ test('regulated organizations can be unpublished', function () { seed(SectorSeeder::class); - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create([ @@ -307,8 +308,8 @@ })->with('regulatedOrganizationIsPublishable'); test('users with admin role can update other member roles', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $other_user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $other_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -329,7 +330,7 @@ }); test('users without admin role can not update member roles', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'member']) @@ -349,9 +350,9 @@ }); test('only administrator can not downgrade their role', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $other_user = User::factory()->create(['context' => 'regulated-organization']); - $yet_another_user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $other_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $yet_another_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -387,7 +388,7 @@ }); test('users with admin role can invite members', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -405,7 +406,7 @@ }); test('users without admin role can not invite members', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'member']) @@ -423,7 +424,7 @@ }); test('users with admin role can cancel invitations', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); @@ -441,7 +442,7 @@ }); test('users without admin role can not cancel invitations', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'member']) ->create(); @@ -458,8 +459,8 @@ }); test('existing members cannot be invited', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $other_user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $other_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -479,7 +480,7 @@ }); test('invitation can be accepted', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory()->create(); $invitation = Invitation::factory()->create([ 'invitationable_id' => $regulatedOrganization->id, @@ -496,7 +497,7 @@ }); test('invitation can be declined', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory()->create(); $invitation = Invitation::factory()->create([ 'invitationable_id' => $regulatedOrganization->id, @@ -514,8 +515,8 @@ }); test('invitation cannot be accepted by different user', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $other_user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $other_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($other_user, ['role' => 'admin']) ->create(); @@ -536,7 +537,7 @@ }); test('invitation can not be declined by a different user', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory()->create(); $invitation = Invitation::factory()->create([ 'invitationable_id' => $regulatedOrganization->id, @@ -553,8 +554,8 @@ }); test('users with admin role can remove members', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $other_user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $other_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -574,8 +575,8 @@ }); test('users without admin role can not remove members', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $other_user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $other_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'member']) @@ -594,7 +595,7 @@ }); test('sole administrator can not remove themself', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -613,7 +614,7 @@ }); test('users with admin role can delete regulated organizations', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -628,7 +629,7 @@ }); test('users with admin role can not delete regulated organizations with wrong password', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -642,7 +643,7 @@ }); test('users without admin role can not delete regulated organizations', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'member']) @@ -657,8 +658,8 @@ }); test('non members can not delete regulated organizations', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $other_user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $other_user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $otherRegulatedOrganization = RegulatedOrganization::factory() ->hasAttached($other_user, ['role' => 'admin']) @@ -673,7 +674,7 @@ }); test('destroy regulated organization request validation errors', function (array $state, array $errors) { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); @@ -692,7 +693,7 @@ }); test('organization or regulated organization users can not view regulated organizations if they are not oriented', function () { - $organizationUser = User::factory()->create(['context' => 'organization', 'oriented_at' => null]); + $organizationUser = User::factory()->create(['context' => UserContext::Organization->value, 'oriented_at' => null]); $organization = Organization::factory()->hasAttached($organizationUser, ['role' => 'admin'])->create(['oriented_at' => null]); $organizationUser->refresh(); @@ -705,7 +706,7 @@ actingAs($organizationUser)->get(localized_route('regulated-organizations.index')) ->assertOk(); - $regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization', 'oriented_at' => null]); + $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value, 'oriented_at' => null]); $regulatedOrganization = RegulatedOrganization::factory()->hasAttached($regulatedOrganizationUser, ['role' => 'admin'])->create(['oriented_at' => null]); $regulatedOrganizationUser->refresh(); @@ -752,7 +753,7 @@ seed(SectorSeeder::class); $user = User::factory()->create(); - $admin = User::factory()->create(['context' => 'regulated-organization']); + $admin = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory()->hasAttached($admin, ['role' => 'admin'])->create([ 'name' => [ 'en' => 'Canada Revenue Agency', @@ -789,7 +790,7 @@ test('regulated organization cannot be previewed until publishable', function () { seed(SectorSeeder::class); - $admin = User::factory()->create(['context' => 'regulated-organization']); + $admin = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory()->hasAttached($admin, ['role' => 'admin'])->create([ 'name' => [ 'en' => 'Canada Revenue Agency', @@ -930,7 +931,7 @@ }); test('regulated organization’s preferred locale is set based on contact person’s locale', function () { - $user = User::factory()->create(['context' => 'regulated-organization', 'locale' => 'en']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value, 'locale' => 'en']); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(['contact_person_email' => $user->email]); diff --git a/tests/Feature/ResourceCollectionTest.php b/tests/Feature/ResourceCollectionTest.php index 3a6b29cc1..4b96f562e 100644 --- a/tests/Feature/ResourceCollectionTest.php +++ b/tests/Feature/ResourceCollectionTest.php @@ -1,5 +1,6 @@ create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $resourceCollection = ResourceCollection::factory()->create(); actingAs($user)->get(localized_route('resource-collections.index')) diff --git a/tests/Feature/ResourceTest.php b/tests/Feature/ResourceTest.php index 2ab91ba13..507816d2b 100644 --- a/tests/Feature/ResourceTest.php +++ b/tests/Feature/ResourceTest.php @@ -2,6 +2,7 @@ use App\Enums\ConsultationPhase; use App\Enums\ResourceFormat; +use App\Enums\UserContext; use App\Models\ContentType; use App\Models\Impact; use App\Models\Resource; @@ -46,7 +47,7 @@ seed(ContentTypeSeeder::class); $user = User::factory()->create(); - $administrator = User::factory()->create(['context' => 'administrator']); + $administrator = User::factory()->create(['context' => UserContext::Administrator->value]); $resource = Resource::factory()->create(); actingAs($user)->get(localized_route('resources.index')) diff --git a/tests/Feature/SuspensionTest.php b/tests/Feature/SuspensionTest.php index 86bc974b2..3ded26a3f 100644 --- a/tests/Feature/SuspensionTest.php +++ b/tests/Feature/SuspensionTest.php @@ -5,6 +5,7 @@ use App\Enums\OrganizationRole; use App\Enums\ProvinceOrTerritory; use App\Enums\StaffHaveLivedExperience; +use App\Enums\UserContext; use App\Models\Engagement; use App\Models\Identity; use App\Models\Impact; @@ -22,7 +23,7 @@ beforeEach(function () { $this->suspendedUser = User::factory()->create(['suspended_at' => now()]); - $this->adminUser = User::factory()->create(['context' => 'administrator']); + $this->adminUser = User::factory()->create(['context' => UserContext::Administrator->value]); seed(IdentitySeeder::class); seed(ImpactSeeder::class); @@ -46,7 +47,7 @@ ]); $this->consultant = $this->consultantUser->individual->fresh(); - $this->organizationUser = User::factory()->create(['context' => 'organization']); + $this->organizationUser = User::factory()->create(['context' => UserContext::Organization->value]); $this->organization = Organization::factory()->create([ 'contact_person_name' => $this->organizationUser->name, 'contact_person_email' => $this->organizationUser->email, @@ -93,7 +94,7 @@ ]); $this->project = $this->engagement->project; $this->regulatedOrganization = $this->project->projectable; - $this->regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); + $this->regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $this->project->update([ 'estimate_approved_at' => now(), 'agreement_received_at' => now(), diff --git a/tests/Feature/UserSettingsTest.php b/tests/Feature/UserSettingsTest.php index e447958d8..aa3b9f18c 100644 --- a/tests/Feature/UserSettingsTest.php +++ b/tests/Feature/UserSettingsTest.php @@ -1,5 +1,6 @@ assertRedirect(localized_route('login')); + get(localized_route('settings.show'))->assertRedirect(localized_route('login')); }); test('individual users can manage access needs', function () { seed(AccessSupportSeeder::class); - $user = User::factory()->create(['context' => 'individual']); + $user = User::factory()->create(['context' => UserContext::Individual->value]); $individual = $user->individual; $individual->update(['region' => 'NL']); @@ -56,10 +56,20 @@ expect($individual->region)->toEqual('NL'); }); +test('only individual users are created with notifications settings', function (string $context) { + $user = User::factory()->create(['context' => $context]); + + if ($context === UserContext::Individual->value) { + expect($user->notification_settings->get('engagements'))->toBe('1'); + } else { + expect($user->notification_settings->get('engagements'))->toBeNull(); + } +})->with(array_column(UserContext::cases(), 'value')); + test('other users cannot manage access needs', function () { seed(AccessSupportSeeder::class); - $user = User::factory()->create(['context' => 'organization']); + $user = User::factory()->create(['context' => UserContext::Organization->value]); actingAs($user)->get(localized_route('settings.edit-access-needs')) ->assertForbidden(); @@ -72,7 +82,7 @@ seed(AccessSupportSeeder::class); $otherAccessNeed = 'Other access need'; - $user = User::factory()->create(['context' => 'individual']); + $user = User::factory()->create(['context' => UserContext::Individual->value]); $individual = $user->individual; actingAs($user)->get(localized_route('settings.edit-access-needs')) @@ -110,7 +120,7 @@ seed(AccessSupportSeeder::class); $engagement = Engagement::factory()->create(); - $user = User::factory()->create(['context' => 'individual']); + $user = User::factory()->create(['context' => UserContext::Individual->value]); // access needs page without engagement parameter actingAs($user)->get(localized_route('settings.edit-access-needs')) @@ -129,7 +139,7 @@ }); test('individual users can manage communication and consultation preferences', function () { - $user = User::factory()->create(['context' => 'individual']); + $user = User::factory()->create(['context' => UserContext::Individual->value]); $user->individual->roles = ['participant']; $user->individual->save(); @@ -192,7 +202,7 @@ }); test('other users cannot manage communication and consultation preferences', function () { - $user = User::factory()->create(['context' => 'organization']); + $user = User::factory()->create(['context' => UserContext::Organization->value]); actingAs($user)->get(localized_route('settings.edit-communication-and-consultation-preferences')) ->assertForbidden(); @@ -202,7 +212,7 @@ }); test('users can manage language preferences', function () { - $user = User::factory()->create(['context' => 'individual', 'locale' => 'asl']); + $user = User::factory()->create(['context' => UserContext::Individual->value, 'locale' => 'asl']); actingAs($user)->get(localized_route('settings.edit-language-preferences')) ->assertOk() @@ -219,7 +229,7 @@ expect($user->locale)->toEqual('asl'); expect($user->individual->first_language)->toEqual('asl'); - $newUser = User::factory()->create(['context' => 'organization']); + $newUser = User::factory()->create(['context' => UserContext::Organization->value]); actingAs($newUser)->get(localized_route('settings.edit-language-preferences')) ->assertOk(); @@ -236,7 +246,7 @@ test('individual user can manage payment information settings', function () { seed(PaymentTypeSeeder::class); - $user = User::factory()->create(['context' => 'individual']); + $user = User::factory()->create(['context' => UserContext::Individual->value]); actingAs($user)->get(localized_route('settings.edit-payment-information')) ->assertOk(); @@ -261,7 +271,7 @@ }); test('other users cannot access payment information settings', function () { - $user = User::factory()->create(['context' => 'organization']); + $user = User::factory()->create(['context' => UserContext::Organization->value]); actingAs($user)->get(localized_route('settings.edit-payment-information')) ->assertForbidden(); @@ -273,8 +283,7 @@ }); test('guest cannot access payment information settings', function () { - get(localized_route('settings.edit-payment-information')) - ->assertRedirect(localized_route('login')); + get(localized_route('settings.edit-payment-information'))->assertRedirect(localized_route('login')); put(localized_route('settings.update-payment-information'), [ 'other' => 1, @@ -283,7 +292,7 @@ }); test('individual user must provide either a predefined payment type or a custom payment type', function () { - $user = User::factory()->create(['context' => 'individual']); + $user = User::factory()->create(['context' => UserContext::Individual->value]); actingAs($user)->from(localized_route('settings.edit-payment-information')) ->put(localized_route('settings.update-payment-information'), [ @@ -305,7 +314,7 @@ seed(SectorSeeder::class); seed(ImpactSeeder::class); - $user = User::factory()->create(['context' => 'individual']); + $user = User::factory()->create(['context' => UserContext::Individual->value]); actingAs($user)->get(localized_route('settings.edit-areas-of-interest')) ->assertOk(); @@ -319,7 +328,7 @@ seed(SectorSeeder::class); seed(ImpactSeeder::class); - $user = User::factory()->create(['context' => 'organization']); + $user = User::factory()->create(['context' => UserContext::Organization->value]); actingAs($user)->get(localized_route('settings.edit-areas-of-interest')) ->assertForbidden(); @@ -343,59 +352,96 @@ }); test('guests can not edit website accessibility preferences', function () { - get(localized_route('settings.edit-website-accessibility-preferences')) - ->assertRedirect(localized_route('login')); + get(localized_route('settings.edit-website-accessibility-preferences'))->assertRedirect(localized_route('login')); }); test('individual and organization users can edit notification preferences', function () { - $user = User::factory()->create(['context' => 'individual']); + $user = User::factory()->create([ + 'context' => UserContext::Individual->value, + 'notification_settings' => [ + 'engagements' => '0', + ], + ]); actingAs($user)->get(localized_route('settings.edit-notification-preferences')) ->assertOk(); actingAs($user)->put(localized_route('settings.update-notification-preferences'), [ - 'preferred_notification_method' => 'email', + 'notification_settings' => ['engagements' => '1'], ]) ->assertSessionHasNoErrors() ->assertRedirect(localized_route('settings.show')); - $user = User::factory()->create(['context' => 'organization']); + expect($user->notification_settings->get('engagements'))->toBe('1'); + + $user = User::factory()->create(['context' => UserContext::Organization->value]); Organization::factory() ->hasAttached($user, ['role' => 'admin']) - ->create(); + ->create([ + 'notification_settings' => [ + 'engagements' => '0', + ], + ]); actingAs($user)->get(localized_route('settings.edit-notification-preferences')) ->assertOk(); actingAs($user)->put(localized_route('settings.update-notification-preferences'), [ - 'preferred_notification_method' => 'email', + 'notification_settings' => ['engagements' => '1'], ]) ->assertSessionHasNoErrors() ->assertRedirect(localized_route('settings.show')); + + expect($user->organization->notification_settings->get('engagements'))->toBe('1'); }); test('other users cannot edit notification preferences', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); actingAs($user)->get(localized_route('settings.edit-notification-preferences')) ->assertForbidden(); - actingAs($user)->put(localized_route('settings.update-notification-preferences'), []) - ->assertForbidden(); + actingAs($user)->put(localized_route('settings.update-notification-preferences'), [ + 'notification_settings' => ['engagements' => '1'], + ])->assertForbidden(); }); test('guests can not edit notification preferences', function () { - get(localized_route('settings.edit-notification-preferences')) - ->assertRedirect(localized_route('login')); + get(localized_route('settings.edit-notification-preferences'))->assertRedirect(localized_route('login')); }); +test('update notification preferences request validation errors', function (array $state, array $errors) { + $user = User::factory()->create([ + 'context' => UserContext::Individual->value, + 'notification_settings' => [ + 'engagements' => '0', + ], + ]); + + actingAs($user)->put(localized_route('settings.update-notification-preferences'), $state) + ->assertSessionHasErrors($errors); + + $user = User::factory()->create(['context' => UserContext::Organization->value]); + Organization::factory() + ->hasAttached($user, ['role' => 'admin']) + ->create([ + 'notification_settings' => [ + 'engagements' => '0', + ], + ]); + + actingAs($user)->put(localized_route('settings.update-notification-preferences'), $state) + ->assertSessionHasErrors($errors); + +})->with('updateNotificationPreferencesRequestValidationErrors'); + test('users belonging to an organization or regulated organization can edit roles and permissions', function () { - $organizationUserWithoutOrganization = User::factory()->create(['context' => 'organization']); + $organizationUserWithoutOrganization = User::factory()->create(['context' => UserContext::Organization->value]); actingAs($organizationUserWithoutOrganization)->get(localized_route('settings.edit-roles-and-permissions')) ->assertForbidden(); - $organizationUserWithOrganization = User::factory()->create(['context' => 'organization']); + $organizationUserWithOrganization = User::factory()->create(['context' => UserContext::Organization->value]); Organization::factory() ->hasAttached($organizationUserWithOrganization, ['role' => 'admin']) @@ -410,12 +456,12 @@ actingAs($organizationUserWithOrganization)->get(localized_route('settings.edit-roles-and-permissions')) ->assertForbidden(); - $regulatedOrganizationUserWithoutOrganization = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationUserWithoutOrganization = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); actingAs($regulatedOrganizationUserWithoutOrganization)->get(localized_route('settings.edit-roles-and-permissions')) ->assertForbidden(); - $regulatedOrganizationUserWithOrganization = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationUserWithOrganization = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); RegulatedOrganization::factory() ->hasAttached($regulatedOrganizationUserWithOrganization, ['role' => 'admin']) @@ -432,7 +478,7 @@ }); test('users belonging to an organization or regulated organization can invite new members to their organization or regulated organization', function () { - $regulatedOrganizationUser = User::factory()->create(['context' => 'regulated-organization']); + $regulatedOrganizationUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $regulatedOrganization = RegulatedOrganization::factory() ->hasAttached($regulatedOrganizationUser, ['role' => 'admin']) ->create(); @@ -442,7 +488,7 @@ ->assertOk() ->assertSee('name="invitationable_type" id="invitationable_type" type="hidden" value="App\Models\RegulatedOrganization"', false); - $organizationUser = User::factory()->create(['context' => 'organization']); + $organizationUser = User::factory()->create(['context' => UserContext::Organization->value]); $organization = Organization::factory() ->hasAttached($organizationUser, ['role' => 'admin']) ->create(); @@ -459,8 +505,7 @@ }); test('guests can not edit roles and permissions', function () { - get(localized_route('settings.edit-roles-and-permissions')) - ->assertRedirect(localized_route('login')); + get(localized_route('settings.edit-roles-and-permissions'))->assertRedirect(localized_route('login')); }); test('email can be changed', function () { @@ -565,7 +610,7 @@ }); test('users cannot delete their own accounts without assigning other admin to organization', function () { - $user = User::factory()->create(); + $user = User::factory()->create(['context' => UserContext::Organization->value]); Organization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); @@ -576,7 +621,7 @@ }); test('users cannot delete their own accounts without assigning other admin to regulatedOrganization', function () { - $user = User::factory()->create(); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) ->create(); diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index ae03fbb6b..434ca0128 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -14,7 +14,7 @@ test('users can view the introduction', function () { $user = User::factory()->create(); - $user->update(['context' => 'individual']); + $user->update(['context' => UserContext::Individual->value]); actingAs($user)->get(localized_route('users.show-introduction')) ->assertOk() @@ -31,7 +31,7 @@ expect($user->finished_introduction)->toBeTrue(); - $user->update(['context' => 'organization']); + $user->update(['context' => UserContext::Organization->value]); actingAs($user)->get(localized_route('users.show-introduction')) ->assertOk() @@ -47,7 +47,7 @@ actingAs($user)->get(localized_route('dashboard')) ->assertRedirect(localized_route('organizations.show-type-selection')); - $user->update(['context' => 'regulated-organization']); + $user->update(['context' => UserContext::RegulatedOrganization->value]); actingAs($user)->get(localized_route('users.show-introduction')) ->assertOk() @@ -63,7 +63,7 @@ actingAs($user)->get(localized_route('dashboard')) ->assertRedirect(localized_route('regulated-organizations.show-type-selection')); - $user->update(['context' => 'training-participant']); + $user->update(['context' => UserContext::TrainingParticipant->value]); actingAs($user)->get(localized_route('users.show-introduction')) ->assertOk(); @@ -198,8 +198,8 @@ }); test('user is only admin of an organization', function () { - $user = User::factory()->create(['context' => 'organization']); - $anotherUser = User::factory()->create(['context' => 'organization']); + $user = User::factory()->create(['context' => UserContext::Organization->value]); + $anotherUser = User::factory()->create(['context' => UserContext::Organization->value]); $organization = Organization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -214,8 +214,8 @@ }); test('user is only admin of a regulated organization', function () { - $user = User::factory()->create(['context' => 'regulated-organization']); - $anotherUser = User::factory()->create(['context' => 'regulated-organization']); + $user = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); + $anotherUser = User::factory()->create(['context' => UserContext::RegulatedOrganization->value]); $organization = RegulatedOrganization::factory() ->hasAttached($user, ['role' => 'admin']) @@ -236,7 +236,7 @@ test('administrative user can be retrieved via query scope', function () { $user = User::factory()->create(); - $adminUser = User::factory()->create(['context' => 'administrator']); + $adminUser = User::factory()->create(['context' => UserContext::Administrator->value]); $users = User::all()->pluck('id')->toArray(); /** @see https://github.com/spatie/laravel-ciphersweet/discussions/51 */ diff --git a/tests/RequestFactories/UpdateIndividualConstituenciesRequestFactory.php b/tests/RequestFactories/UpdateIndividualConstituenciesRequestFactory.php index 5b7fc139d..490a71155 100644 --- a/tests/RequestFactories/UpdateIndividualConstituenciesRequestFactory.php +++ b/tests/RequestFactories/UpdateIndividualConstituenciesRequestFactory.php @@ -16,7 +16,7 @@ public function definition(): array 'base_disability_type' => 'specific_disabilities', 'has_other_disability_connection' => 1, 'other_disability_connection' => ['en' => 'Something not listed'], - 'area_type_connections' => [Identity::whereJsonContains('clusters', IdentityCluster::Area)->first()->id], + 'area_type_connections' => [Identity::whereJsonContains('clusters', IdentityCluster::Area)->first()->id ?? Identity::factory()->create(['clusters' => [IdentityCluster::Area->value]])], 'has_indigenous_connections' => 0, 'refugees_and_immigrants' => 0, 'has_gender_and_sexuality_connections' => 0,