diff --git a/backend/Directory.Build.props b/backend/Directory.Build.props index b81d75c4147..15e6c17e05b 100644 --- a/backend/Directory.Build.props +++ b/backend/Directory.Build.props @@ -4,5 +4,8 @@ <Company>Altinn</Company> <Product>Altinn Studio</Product> </PropertyGroup> + <PropertyGroup> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + </PropertyGroup> </Project> diff --git a/backend/Migrations.Dockerfile b/backend/Migrations.Dockerfile index 227b6c86db8..94eb2973f73 100644 --- a/backend/Migrations.Dockerfile +++ b/backend/Migrations.Dockerfile @@ -13,7 +13,7 @@ ENV OidcLoginSettings__ClientSecret=dummyRequired RUN dotnet ef migrations script --project src/Designer/Designer.csproj --idempotent -o /app/migrations.sql -FROM alpine:3.21.0 AS final +FROM alpine:3.21.2 AS final COPY --from=build /app/migrations.sql migrations.sql RUN apk --no-cache add postgresql-client diff --git a/backend/packagegroups/NuGet.props b/backend/packagegroups/NuGet.props index ca24e16542e..eb7f73dab54 100644 --- a/backend/packagegroups/NuGet.props +++ b/backend/packagegroups/NuGet.props @@ -2,8 +2,8 @@ <ItemGroup Label="Altinn specific packages"> <PackageReference Update="Altinn.App.Core" Version="8.0.0-rc1" /> - <PackageReference Update="Altinn.Common.AccessToken" Version="4.5.4" /> - <PackageReference Update="Altinn.Common.AccessTokenClient" Version="3.0.10" /> + <PackageReference Update="Altinn.Common.AccessToken" Version="4.5.5" /> + <PackageReference Update="Altinn.Common.AccessTokenClient" Version="3.0.11" /> <PackageReference Update="Altinn.Platform.Storage.Interface" Version="3.34.0" /> </ItemGroup> @@ -18,58 +18,57 @@ <PackageReference Update="Microsoft.ApplicationInsights.Kubernetes" Version="7.0.1" /> <PackageReference Update="Microsoft.AspNet.WebApi.Client" Version="6.0.0" /> <PackageReference Update="Microsoft.AspNetCore.DataProtection.AzureKeyVault" Version="3.1.24" /> - <PackageReference Update="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> - <PackageReference Update="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.0" /> + <PackageReference Update="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" /> + <PackageReference Update="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.1" /> <PackageReference Update="Microsoft.Azure.KeyVault" Version="3.0.5" /> <PackageReference Update="Microsoft.Azure.Services.AppAuthentication" Version="1.6.2" /> <PackageReference Update="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.24" /> - <PackageReference Update="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0" /> - <PackageReference Update="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0" /> - <PackageReference Update="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="9.0.0" /> + <PackageReference Update="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.1" /> + <PackageReference Update="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.1" /> + <PackageReference Update="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="9.0.1" /> <PackageReference Update="Microsoft.VisualStudio.Web.BrowserLink" Version="2.2.0" /> - <PackageReference Update="Microsoft.AspNetCore.OpenApi" Version="9.0.0" /> + <PackageReference Update="Microsoft.AspNetCore.OpenApi" Version="9.0.1" /> <PackageReference Update="HtmlAgilityPack" Version="1.11.72" /> <PackageReference Update="Microsoft.DiaSymReader.Native" Version="1.7.0" /> - <PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" /> - <PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0" /> + <PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" /> + <PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1" /> <PackageReference Update="Microsoft.FeatureManagement.AspNetCore" Version="4.0.0" /> <PackageReference Update="Scrutor" Version="5.1.1" /> - <PackageReference Update="Polly" Version="8.5.0" /> + <PackageReference Update="Polly" Version="8.5.1" /> <PackageReference Update="Altinn.Authorization.ABAC" Version="0.0.8" /> <PackageReference Update="Altinn.ApiClients.Maskinporten" Version="9.2.0" /> <PackageReference Update="MediatR" Version="12.4.1" /> <PackageReference Update="DotNetEnv" Version="3.1.1" /> <PackageReference Update="NuGet.Versioning" Version="6.12.1" /> <PackageReference Update="DistributedLock.Postgres" Version="1.2.1" /> - <PackageReference Include="System.Text.Json" Version="[9.0.0]" /> + <PackageReference Include="System.Text.Json" Version="[9.0.1]" /> <PackageReference Update="Microsoft.CodeAnalysis.Common" Version="[4.8.0, 4.12.0)" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="[4.12.0]" /> <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="[4.12.0]" /> <PackageReference Update="Azure.Security.KeyVault.Secrets" Version="[4.7.0]" /> - <PackageReference Include="System.Formats.Asn1" Version="8.0.1" /> + <PackageReference Include="System.Formats.Asn1" Version="9.0.1" /> <PackageReference Include="System.Net.Http" Version="[4.3.4]" /> <PackageReference Include="System.Text.RegularExpressions" Version="[4.3.1]" /> <PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.24" /> </ItemGroup> <ItemGroup Label="Packages used for testing"> - <PackageReference Update="FluentAssertions" Version="7.0.0" /> - <PackageReference Update="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" /> + <PackageReference Update="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" /> <PackageReference Update="Microsoft.NET.Test.Sdk" Version="17.12.0" /> <!-- Do not upgrade Moq version from 4.18.4 --> <PackageReference Update="Moq" Version="4.18.4" /> - <PackageReference Update="xunit" Version="2.9.2" /> + <PackageReference Update="xunit" Version="2.9.3" /> <PackageReference Update="xunit.runner.visualstudio" Version="2.8.2" /> <PackageReference Update="coverlet.collector" Version="6.0.3" /> <PackageReference Update="Basic.Reference.Assemblies" Version="1.7.8" /> <PackageReference Update="Fare" Version="2.2.1" /> - <PackageReference Update="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> + <PackageReference Update="Microsoft.AspNetCore.Mvc" Version="2.3.0" /> <PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" /> <PackageReference Update="Microsoft.CodeAnalysis.Common" Version="4.12.0" /> <PackageReference Update="Testcontainers" Version="4.1.0" /> <PackageReference Update="Testcontainers.PostgreSql" Version="3.10.0" /> - <PackageReference Update="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" /> - <PackageReference Update="Microsoft.Extensions.DependencyModel" Version="9.0.0" /> + <PackageReference Update="Microsoft.AspNetCore.SignalR.Client" Version="9.0.1" /> + <PackageReference Update="Microsoft.Extensions.DependencyModel" Version="9.0.1" /> <PackageReference Update="WireMock.Net" Version="1.6.11" /> <PackageReference Update="DistributedLock.FileSystem" Version="1.0.3" /> </ItemGroup> diff --git a/backend/src/Designer/Controllers/AppDevelopmentController.cs b/backend/src/Designer/Controllers/AppDevelopmentController.cs index ae927c4eb05..5f8d9e884ca 100644 --- a/backend/src/Designer/Controllers/AppDevelopmentController.cs +++ b/backend/src/Designer/Controllers/AppDevelopmentController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,6 @@ using Altinn.Studio.Designer.Events; using Altinn.Studio.Designer.Filters; using Altinn.Studio.Designer.Helpers; -using Altinn.Studio.Designer.Infrastructure.GitRepository; using Altinn.Studio.Designer.Models; using Altinn.Studio.Designer.Models.Dto; using Altinn.Studio.Designer.Services.Interfaces; @@ -32,7 +32,6 @@ public class AppDevelopmentController : Controller private readonly IAppDevelopmentService _appDevelopmentService; private readonly IRepository _repository; private readonly ISourceControl _sourceControl; - private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory; private readonly ApplicationInsightsSettings _applicationInsightsSettings; private readonly IMediator _mediator; @@ -43,15 +42,13 @@ public class AppDevelopmentController : Controller /// <param name="appDevelopmentService">The app development service</param> /// <param name="repositoryService">The application repository service</param> /// <param name="sourceControl">The source control service.</param> - /// <param name="altinnGitRepositoryFactory"></param> /// <param name="applicationInsightsSettings">An <see cref="ApplicationInsightsSettings"/></param> /// <param name="mediator"></param> - public AppDevelopmentController(IAppDevelopmentService appDevelopmentService, IRepository repositoryService, ISourceControl sourceControl, IAltinnGitRepositoryFactory altinnGitRepositoryFactory, ApplicationInsightsSettings applicationInsightsSettings, IMediator mediator) + public AppDevelopmentController(IAppDevelopmentService appDevelopmentService, IRepository repositoryService, ISourceControl sourceControl, ApplicationInsightsSettings applicationInsightsSettings, IMediator mediator) { _appDevelopmentService = appDevelopmentService; _repository = repositoryService; _sourceControl = sourceControl; - _altinnGitRepositoryFactory = altinnGitRepositoryFactory; _applicationInsightsSettings = applicationInsightsSettings; _mediator = mediator; } @@ -123,8 +120,17 @@ public async Task<ActionResult> SaveFormLayout(string org, string app, [FromQuer if (formLayoutPayload.ComponentIdsChange is not null && !string.IsNullOrEmpty(layoutSetName)) { - foreach (var componentIdChange in formLayoutPayload.ComponentIdsChange) + foreach (var componentIdChange in formLayoutPayload.ComponentIdsChange.Where((componentIdChange) => componentIdChange.OldComponentId != componentIdChange.NewComponentId)) { + if (componentIdChange.NewComponentId == null) + { + await _mediator.Publish(new ComponentDeletedEvent + { + ComponentId = componentIdChange.OldComponentId, + LayoutSetName = layoutSetName, + EditingContext = editingContext + }, cancellationToken); + } await _mediator.Publish(new ComponentIdChangedEvent { OldComponentId = componentIdChange.OldComponentId, @@ -159,16 +165,26 @@ await _mediator.Publish(new LayoutPageAddedEvent /// <param name="app">Application identifier which is unique within an organisation.</param> /// <param name="layoutSetName">The name of the layout set the specific layout belongs to</param> /// <param name="layoutName">The form layout to be deleted</param> + /// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param> /// <returns>A success message if the save was successful</returns> [HttpDelete] [Route("form-layout/{layoutName}")] - public ActionResult DeleteFormLayout(string org, string app, [FromQuery] string layoutSetName, [FromRoute] string layoutName) + public async Task<ActionResult> DeleteFormLayout(string org, string app, [FromQuery] string layoutSetName, [FromRoute] string layoutName, CancellationToken cancellationToken) { try { string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); var editingContext = AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer); + + await _mediator.Publish(new LayoutPageDeletedEvent + { + EditingContext = editingContext, + LayoutSetName = layoutSetName, + LayoutName = layoutName, + }, cancellationToken); + _appDevelopmentService.DeleteFormLayout(editingContext, layoutSetName, layoutName); + return Ok(); } catch (FileNotFoundException exception) @@ -402,13 +418,15 @@ public async Task<ActionResult> DeleteLayoutSet(string org, string app, [FromRou { string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); var editingContext = AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer); - LayoutSets layoutSets = await _appDevelopmentService.DeleteLayoutSet(editingContext, layoutSetIdToUpdate, cancellationToken); await _mediator.Publish(new LayoutSetDeletedEvent { EditingContext = editingContext, - LayoutSetId = layoutSetIdToUpdate + LayoutSetName = layoutSetIdToUpdate }, cancellationToken); + + LayoutSets layoutSets = await _appDevelopmentService.DeleteLayoutSet(editingContext, layoutSetIdToUpdate, cancellationToken); + return Ok(layoutSets); } @@ -543,23 +561,6 @@ public ActionResult GetWidgetSettings(string org, string app) return Ok(widgetSettings); } - [HttpGet] - [Route("option-list-ids")] - public ActionResult GetOptionListIds(string org, string app) - { - try - { - string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); - AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer); - string[] optionListIds = altinnAppGitRepository.GetOptionsListIds(); - return Ok(optionListIds); - } - catch (LibGit2Sharp.NotFoundException) - { - return NoContent(); - } - } - [HttpGet("app-version")] public VersionResponse GetAppVersion(string org, string app) { diff --git a/backend/src/Designer/Controllers/ContactController.cs b/backend/src/Designer/Controllers/ContactController.cs new file mode 100644 index 00000000000..c32609a1dd8 --- /dev/null +++ b/backend/src/Designer/Controllers/ContactController.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using Altinn.Studio.Designer.Helpers; +using Altinn.Studio.Designer.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Studio.Designer.Controllers +{ + [Route("designer/api/[controller]")] + [ApiController] + public class ContactController : ControllerBase + { + private readonly IGitea _giteaService; + + public ContactController(IGitea giteaService) + { + _giteaService = giteaService; + } + + [AllowAnonymous] + [HttpGet("belongs-to-org")] + public async Task<IActionResult> BelongsToOrg() + { + bool isNotAuthenticated = !AuthenticationHelper.IsAuthenticated(HttpContext); + if (isNotAuthenticated) + { + return Ok(new BelongsToOrgDto { BelongsToOrg = false }); + } + + try + { + var organizations = await _giteaService.GetUserOrganizations(); + return Ok(new BelongsToOrgDto { BelongsToOrg = organizations.Count > 0 }); + } + catch (Exception) + { + return Ok(new BelongsToOrgDto { BelongsToOrg = false }); + } + } + } +} diff --git a/backend/src/Designer/Controllers/ResourceAdminController.cs b/backend/src/Designer/Controllers/ResourceAdminController.cs index 1601cd5c06a..1e00a003ca9 100644 --- a/backend/src/Designer/Controllers/ResourceAdminController.cs +++ b/backend/src/Designer/Controllers/ResourceAdminController.cs @@ -487,7 +487,7 @@ public async Task<ActionResult<List<AvailableService>>> GetAltinn2LinkServices(s foreach (ServiceResource resource in allResources) { if (resource?.HasCompetentAuthority.Orgcode != null - && resource.ResourceReferences != null && resource.ResourceReferences.Exists(r => r.ReferenceType != null && r.ReferenceType.Equals(ReferenceType.ServiceCode)) + && resource.ResourceReferences != null && resource.ResourceReferences.Exists(r => r.ReferenceType != null && r.ReferenceType.Equals(ResourceReferenceType.ServiceCode)) && resource.ResourceType == ResourceType.Altinn2Service) { AvailableService service = new AvailableService(); @@ -496,8 +496,8 @@ public async Task<ActionResult<List<AvailableService>>> GetAltinn2LinkServices(s service.ServiceName = resource.Title["nb"]; } - service.ExternalServiceCode = resource.ResourceReferences.First(r => r.ReferenceType.Equals(ReferenceType.ServiceCode)).Reference; - service.ExternalServiceEditionCode = Convert.ToInt32(resource.ResourceReferences.First(r => r.ReferenceType.Equals(ReferenceType.ServiceEditionCode)).Reference); + service.ExternalServiceCode = resource.ResourceReferences.First(r => r.ReferenceType.Equals(ResourceReferenceType.ServiceCode)).Reference; + service.ExternalServiceEditionCode = Convert.ToInt32(resource.ResourceReferences.First(r => r.ReferenceType.Equals(ResourceReferenceType.ServiceEditionCode)).Reference); service.ServiceOwnerCode = resource.HasCompetentAuthority.Orgcode; unfiltered.Add(service); } @@ -551,7 +551,7 @@ private ValidationProblemDetails ValidateResource(ServiceResource resource) if (resource.ResourceType == ResourceType.MaskinportenSchema) { - if (resource.ResourceReferences == null || !resource.ResourceReferences.Any((x) => x.ReferenceType == ReferenceType.MaskinportenScope)) + if (resource.ResourceReferences == null || !resource.ResourceReferences.Any((x) => x.ReferenceType == ResourceReferenceType.MaskinportenScope)) { ModelState.AddModelError($"{resource.Identifier}.resourceReferences", "resourceerror.missingmaskinportenscope"); } diff --git a/backend/src/Designer/Controllers/UserController.cs b/backend/src/Designer/Controllers/UserController.cs index 5e81574fd1f..a5870705491 100644 --- a/backend/src/Designer/Controllers/UserController.cs +++ b/backend/src/Designer/Controllers/UserController.cs @@ -27,6 +27,7 @@ public class UserController : ControllerBase /// </summary> /// <param name="giteaWrapper">the gitea wrapper</param> /// <param name="antiforgery">Access to the antiforgery system in .NET Core</param> + /// <param name="userService">User service</param> public UserController(IGitea giteaWrapper, IAntiforgery antiforgery, IUserService userService) { _giteaApi = giteaWrapper; diff --git a/backend/src/Designer/Designer.csproj b/backend/src/Designer/Designer.csproj index 37ff2b0dedf..6b433f8c16c 100644 --- a/backend/src/Designer/Designer.csproj +++ b/backend/src/Designer/Designer.csproj @@ -77,7 +77,6 @@ <Watch Remove="Configuration\ResourceRegistryMaskinportenIntegrationSettings.cs" /> <Watch Remove="Controllers\PolicyController.cs" /> <Watch Remove="Configuration\MaskinportenClientSettings.cs" /> - <Watch Remove="Controllers\CompetentAuthority.cs" /> <Watch Remove="Controllers\ResourceAdmController.cs" /> <Watch Remove="Controllers\ResourceAdminController.cs" /> <Watch Remove="Enums\ReferenceSource.cs" /> @@ -95,6 +94,7 @@ <Watch Remove="Models\AccessListMember.cs" /> <Watch Remove="Models\AvailableService.cs" /> <Watch Remove="Models\BrregPartyResultSet.cs" /> + <Watch Remove="Models\CompetentAuthority.cs" /> <Watch Remove="Models\ConceptSchema.cs" /> <Watch Remove="Models\ContactPoint.cs" /> <Watch Remove="Models\CreateAccessListModel.cs" /> diff --git a/backend/src/Designer/Enums/ReferenceType.cs b/backend/src/Designer/Enums/ReferenceType.cs index de88d575e3e..e53ab497980 100644 --- a/backend/src/Designer/Enums/ReferenceType.cs +++ b/backend/src/Designer/Enums/ReferenceType.cs @@ -1,31 +1,9 @@ -using System.Runtime.Serialization; - namespace Altinn.Studio.Designer.Enums { - /// <summary> - /// Enum for reference types of resources in the resource registry - /// </summary> public enum ReferenceType { - [EnumMember(Value = "Default")] - Default = 0, - - [EnumMember(Value = "Uri")] - Uri = 1, - - [EnumMember(Value = "DelegationSchemeId")] - DelegationSchemeId = 2, - - [EnumMember(Value = "MaskinportenScope")] - MaskinportenScope = 3, - - [EnumMember(Value = "ServiceCode")] - ServiceCode = 4, - - [EnumMember(Value = "ServiceEditionCode")] - ServiceEditionCode = 5, - - [EnumMember(Value = "ApplicationId")] - ApplicationId = 6, + LayoutSet, + Layout, + Component } } diff --git a/backend/src/Designer/Enums/ReferenceSource.cs b/backend/src/Designer/Enums/ResourceReferenceSource.cs similarity index 93% rename from backend/src/Designer/Enums/ReferenceSource.cs rename to backend/src/Designer/Enums/ResourceReferenceSource.cs index f7d81958834..502d82cad96 100644 --- a/backend/src/Designer/Enums/ReferenceSource.cs +++ b/backend/src/Designer/Enums/ResourceReferenceSource.cs @@ -5,7 +5,7 @@ namespace Altinn.Studio.Designer.Enums /// <summary> /// Enum for the different reference sources for resources in the resource registry /// </summary> - public enum ReferenceSource + public enum ResourceReferenceSource { [EnumMember(Value = "Default")] Default = 0, diff --git a/backend/src/Designer/Enums/ResourceReferenceType.cs b/backend/src/Designer/Enums/ResourceReferenceType.cs new file mode 100644 index 00000000000..69b35c7f1d1 --- /dev/null +++ b/backend/src/Designer/Enums/ResourceReferenceType.cs @@ -0,0 +1,31 @@ +using System.Runtime.Serialization; + +namespace Altinn.Studio.Designer.Enums +{ + /// <summary> + /// Enum for reference types of resources in the resource registry + /// </summary> + public enum ResourceReferenceType + { + [EnumMember(Value = "Default")] + Default = 0, + + [EnumMember(Value = "Uri")] + Uri = 1, + + [EnumMember(Value = "DelegationSchemeId")] + DelegationSchemeId = 2, + + [EnumMember(Value = "MaskinportenScope")] + MaskinportenScope = 3, + + [EnumMember(Value = "ServiceCode")] + ServiceCode = 4, + + [EnumMember(Value = "ServiceEditionCode")] + ServiceEditionCode = 5, + + [EnumMember(Value = "ApplicationId")] + ApplicationId = 6, + } +} diff --git a/backend/src/Designer/EventHandlers/ComponentDeleted/ComponentDeletedLayoutsHandler.cs b/backend/src/Designer/EventHandlers/ComponentDeleted/ComponentDeletedLayoutsHandler.cs new file mode 100644 index 00000000000..f2d893f2886 --- /dev/null +++ b/backend/src/Designer/EventHandlers/ComponentDeleted/ComponentDeletedLayoutsHandler.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Altinn.Studio.Designer.Enums; +using Altinn.Studio.Designer.Events; +using Altinn.Studio.Designer.Hubs.SyncHub; +using Altinn.Studio.Designer.Models; +using Altinn.Studio.Designer.Services.Interfaces; +using MediatR; + +namespace Altinn.Studio.Designer.EventHandlers.ComponentDeleted; + +public class ComponentDeletedLayoutsHandler(IFileSyncHandlerExecutor fileSyncHandlerExecutor, IAppDevelopmentService appDevelopmentService) : INotificationHandler<ComponentDeletedEvent> +{ + public async Task Handle(ComponentDeletedEvent notification, CancellationToken cancellationToken) + { + await fileSyncHandlerExecutor.ExecuteWithExceptionHandlingAndConditionalNotification( + notification.EditingContext, + SyncErrorCodes.ComponentDeletedLayoutsSyncError, + "layouts", + async () => + { + List<Reference> referencesToDelete = [new Reference(ReferenceType.Component, notification.LayoutSetName, notification.ComponentId)]; + return await appDevelopmentService.UpdateLayoutReferences(notification.EditingContext, referencesToDelete, cancellationToken); + }); + } +} diff --git a/backend/src/Designer/EventHandlers/LayoutPageDeleted/LayoutPageDeletedLayoutsHandler.cs b/backend/src/Designer/EventHandlers/LayoutPageDeleted/LayoutPageDeletedLayoutsHandler.cs new file mode 100644 index 00000000000..60c60cbd66b --- /dev/null +++ b/backend/src/Designer/EventHandlers/LayoutPageDeleted/LayoutPageDeletedLayoutsHandler.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Altinn.Studio.Designer.Enums; +using Altinn.Studio.Designer.Events; +using Altinn.Studio.Designer.Hubs.SyncHub; +using Altinn.Studio.Designer.Models; +using Altinn.Studio.Designer.Services.Interfaces; +using MediatR; + +namespace Altinn.Studio.Designer.EventHandlers.LayoutPageDeleted; + +public class LayoutPageDeletedLayoutsHandler(IFileSyncHandlerExecutor fileSyncHandlerExecutor, IAppDevelopmentService appDevelopmentService) : INotificationHandler<LayoutPageDeletedEvent> +{ + public async Task Handle(LayoutPageDeletedEvent notification, CancellationToken cancellationToken) + { + await fileSyncHandlerExecutor.ExecuteWithExceptionHandlingAndConditionalNotification( + notification.EditingContext, + SyncErrorCodes.LayoutPageDeletedLayoutsSyncError, + "layouts", + async () => + { + List<Reference> referencesToDelete = [new Reference(ReferenceType.Layout, notification.LayoutSetName, notification.LayoutName)]; + return await appDevelopmentService.UpdateLayoutReferences(notification.EditingContext, referencesToDelete, cancellationToken); + }); + } +} diff --git a/backend/src/Designer/EventHandlers/LayoutSetDeleted/LayoutSetDeletedComponentRefHandler.cs b/backend/src/Designer/EventHandlers/LayoutSetDeleted/LayoutSetDeletedComponentRefHandler.cs deleted file mode 100644 index 95b38685a47..00000000000 --- a/backend/src/Designer/EventHandlers/LayoutSetDeleted/LayoutSetDeletedComponentRefHandler.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Nodes; -using System.Threading; -using System.Threading.Tasks; -using Altinn.App.Core.Helpers; -using Altinn.Studio.Designer.Events; -using Altinn.Studio.Designer.Hubs.SyncHub; -using Altinn.Studio.Designer.Infrastructure.GitRepository; -using Altinn.Studio.Designer.Services.Interfaces; -using MediatR; - -namespace Altinn.Studio.Designer.EventHandlers.LayoutSetDeleted; - -public class LayoutSetDeletedComponentRefHandler(IAltinnGitRepositoryFactory altinnGitRepositoryFactory, IFileSyncHandlerExecutor fileSyncHandlerExecutor) : INotificationHandler<LayoutSetDeletedEvent> -{ - public async Task Handle(LayoutSetDeletedEvent notification, CancellationToken cancellationToken) - { - AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository( - notification.EditingContext.Org, - notification.EditingContext.Repo, - notification.EditingContext.Developer); - - string[] layoutSetNames = altinnAppGitRepository.GetLayoutSetNames(); - - await fileSyncHandlerExecutor.ExecuteWithExceptionHandlingAndConditionalNotification( - notification.EditingContext, - SyncErrorCodes.LayoutSetSubLayoutSyncError, - "layouts", - async () => - { - bool hasChanges = false; - foreach (string layoutSetName in layoutSetNames) - { - Dictionary<string, JsonNode> formLayouts = await altinnAppGitRepository.GetFormLayouts(layoutSetName, cancellationToken); - foreach (var formLayout in formLayouts) - { - hasChanges |= await RemoveComponentsReferencingLayoutSet( - notification, - altinnAppGitRepository, - layoutSetName, - formLayout, - cancellationToken); - } - } - return hasChanges; - }); - } - - private static async Task<bool> RemoveComponentsReferencingLayoutSet(LayoutSetDeletedEvent notification, AltinnAppGitRepository altinnAppGitRepository, string layoutSetName, KeyValuePair<string, JsonNode> formLayout, CancellationToken cancellationToken) - { - if (formLayout.Value["data"] is not JsonObject data || data["layout"] is not JsonArray layoutArray) - { - return false; - } - - bool hasChanges = false; - layoutArray.RemoveAll(jsonNode => - { - if (jsonNode["layoutSet"]?.GetValue<string>() == notification.LayoutSetId) - { - hasChanges = true; - return true; - } - return false; - }); - - if (hasChanges) - { - await altinnAppGitRepository.SaveLayout(layoutSetName, $"{formLayout.Key}.json", formLayout.Value, cancellationToken); - } - return hasChanges; - } -} diff --git a/backend/src/Designer/EventHandlers/LayoutSetDeleted/LayoutSetDeletedLayoutsHandler.cs b/backend/src/Designer/EventHandlers/LayoutSetDeleted/LayoutSetDeletedLayoutsHandler.cs new file mode 100644 index 00000000000..c30309362e0 --- /dev/null +++ b/backend/src/Designer/EventHandlers/LayoutSetDeleted/LayoutSetDeletedLayoutsHandler.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Altinn.Studio.Designer.Enums; +using Altinn.Studio.Designer.Events; +using Altinn.Studio.Designer.Hubs.SyncHub; +using Altinn.Studio.Designer.Models; +using Altinn.Studio.Designer.Services.Interfaces; +using MediatR; + +namespace Altinn.Studio.Designer.EventHandlers.LayoutSetDeleted; + +public class LayoutSetDeletedLayoutsHandler(IFileSyncHandlerExecutor fileSyncHandlerExecutor, IAppDevelopmentService appDevelopmentService) : INotificationHandler<LayoutSetDeletedEvent> +{ + public async Task Handle(LayoutSetDeletedEvent notification, CancellationToken cancellationToken) + { + await fileSyncHandlerExecutor.ExecuteWithExceptionHandlingAndConditionalNotification( + notification.EditingContext, + SyncErrorCodes.LayoutSetDeletedLayoutsSyncError, + "layouts", + async () => + { + List<Reference> referencesToDelete = [new Reference(ReferenceType.LayoutSet, notification.LayoutSetName, notification.LayoutSetName)]; + return await appDevelopmentService.UpdateLayoutReferences(notification.EditingContext, referencesToDelete, cancellationToken); + }); + } +} diff --git a/backend/src/Designer/Events/ComponentDeletedEvent.cs b/backend/src/Designer/Events/ComponentDeletedEvent.cs new file mode 100644 index 00000000000..a6897eee55d --- /dev/null +++ b/backend/src/Designer/Events/ComponentDeletedEvent.cs @@ -0,0 +1,11 @@ +using Altinn.Studio.Designer.Models; +using MediatR; + +namespace Altinn.Studio.Designer.Events; + +public class ComponentDeletedEvent : INotification +{ + public AltinnRepoEditingContext EditingContext { get; set; } + public string LayoutSetName { get; set; } + public string ComponentId { get; set; } +} diff --git a/backend/src/Designer/Events/LayoutPageDeletedEvent.cs b/backend/src/Designer/Events/LayoutPageDeletedEvent.cs new file mode 100644 index 00000000000..8411d583169 --- /dev/null +++ b/backend/src/Designer/Events/LayoutPageDeletedEvent.cs @@ -0,0 +1,11 @@ +using Altinn.Studio.Designer.Models; +using MediatR; + +namespace Altinn.Studio.Designer.Events; + +public class LayoutPageDeletedEvent : INotification +{ + public AltinnRepoEditingContext EditingContext { get; set; } + public string LayoutSetName { get; set; } + public string LayoutName { get; set; } +} diff --git a/backend/src/Designer/Events/LayoutSetDeletedEvent.cs b/backend/src/Designer/Events/LayoutSetDeletedEvent.cs index aadbd44a4c0..00bf4a926cf 100644 --- a/backend/src/Designer/Events/LayoutSetDeletedEvent.cs +++ b/backend/src/Designer/Events/LayoutSetDeletedEvent.cs @@ -5,6 +5,6 @@ namespace Altinn.Studio.Designer.Events; public class LayoutSetDeletedEvent : INotification { - public string LayoutSetId { get; set; } public AltinnRepoEditingContext EditingContext { get; set; } + public string LayoutSetName { get; set; } } diff --git a/backend/src/Designer/Helpers/AuthenticationHelper.cs b/backend/src/Designer/Helpers/AuthenticationHelper.cs index 2675d25ebde..503ee828878 100644 --- a/backend/src/Designer/Helpers/AuthenticationHelper.cs +++ b/backend/src/Designer/Helpers/AuthenticationHelper.cs @@ -23,5 +23,10 @@ public static Task<string> GetDeveloperAppTokenAsync(this HttpContext context) { return context.GetTokenAsync("access_token"); } + + public static bool IsAuthenticated(HttpContext context) + { + return context.User.Identity?.IsAuthenticated ?? false; + } } } diff --git a/backend/src/Designer/Hubs/SyncHub/SyncErrorCodes.cs b/backend/src/Designer/Hubs/SyncHub/SyncErrorCodes.cs index 23556793c7b..04178d4eb7e 100644 --- a/backend/src/Designer/Hubs/SyncHub/SyncErrorCodes.cs +++ b/backend/src/Designer/Hubs/SyncHub/SyncErrorCodes.cs @@ -9,8 +9,10 @@ public static class SyncErrorCodes public const string ApplicationMetadataDataTypeSyncError = nameof(ApplicationMetadataDataTypeSyncError); public const string LayoutSetsDataTypeSyncError = nameof(LayoutSetsDataTypeSyncError); public const string LayoutSetComponentIdSyncError = nameof(LayoutSetComponentIdSyncError); - public const string LayoutSetSubLayoutSyncError = nameof(LayoutSetSubLayoutSyncError); + public const string LayoutSetDeletedLayoutsSyncError = nameof(LayoutSetDeletedLayoutsSyncError); public const string LayoutSetSubFormButtonSyncError = nameof(LayoutSetSubFormButtonSyncError); public const string SettingsComponentIdSyncError = nameof(SettingsComponentIdSyncError); public const string LayoutPageAddSyncError = nameof(LayoutPageAddSyncError); + public const string ComponentDeletedLayoutsSyncError = nameof(ComponentDeletedLayoutsSyncError); + public const string LayoutPageDeletedLayoutsSyncError = nameof(LayoutPageDeletedLayoutsSyncError); } diff --git a/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs b/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs index c173b2aaaf8..7cdb3cae19a 100644 --- a/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs +++ b/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs @@ -798,7 +798,7 @@ public string[] GetOptionsListIds() string optionsFolder = Path.Combine(OptionsFolderPath); if (!DirectoryExistsByRelativePath(optionsFolder)) { - throw new NotFoundException("Options folder not found."); + return []; } string[] fileNames = GetFilesByRelativeDirectoryAscSorted(optionsFolder, "*.json"); diff --git a/backend/src/Designer/Controllers/CompetentAuthority.cs b/backend/src/Designer/Models/CompetentAuthority.cs similarity index 100% rename from backend/src/Designer/Controllers/CompetentAuthority.cs rename to backend/src/Designer/Models/CompetentAuthority.cs diff --git a/backend/src/Designer/Models/Dto/BelongsToOrg.cs b/backend/src/Designer/Models/Dto/BelongsToOrg.cs new file mode 100644 index 00000000000..c79b96767d0 --- /dev/null +++ b/backend/src/Designer/Models/Dto/BelongsToOrg.cs @@ -0,0 +1,7 @@ +using System.Text.Json.Serialization; + +public class BelongsToOrgDto +{ + [JsonPropertyName("belongsToOrg")] + public bool BelongsToOrg { get; set; } +} diff --git a/backend/src/Designer/Models/Reference.cs b/backend/src/Designer/Models/Reference.cs new file mode 100644 index 00000000000..6ba9d9a9c99 --- /dev/null +++ b/backend/src/Designer/Models/Reference.cs @@ -0,0 +1,7 @@ + +using Altinn.Studio.Designer.Enums; + +namespace Altinn.Studio.Designer.Models +{ + public record Reference(ReferenceType Type, string LayoutSetName, string Id, string NewId = null); +} diff --git a/backend/src/Designer/Models/ResourceReference.cs b/backend/src/Designer/Models/ResourceReference.cs index af730c5064a..7dcb6fdb735 100644 --- a/backend/src/Designer/Models/ResourceReference.cs +++ b/backend/src/Designer/Models/ResourceReference.cs @@ -13,7 +13,7 @@ public class ResourceReference /// The source the reference identifier points to /// </summary> [JsonConverter(typeof(JsonStringEnumConverter))] - public ReferenceSource? ReferenceSource { get; set; } + public ResourceReferenceSource? ReferenceSource { get; set; } /// <summary> /// The reference identifier @@ -24,6 +24,6 @@ public class ResourceReference /// The reference type /// </summary> [JsonConverter(typeof(JsonStringEnumConverter))] - public ReferenceType? ReferenceType { get; set; } + public ResourceReferenceType? ReferenceType { get; set; } } } diff --git a/backend/src/Designer/Repository/ORMImplementation/Mappers/DeploymentMapper.cs b/backend/src/Designer/Repository/ORMImplementation/Mappers/DeploymentMapper.cs index e97ad9e8ba2..1e1b11b22d3 100644 --- a/backend/src/Designer/Repository/ORMImplementation/Mappers/DeploymentMapper.cs +++ b/backend/src/Designer/Repository/ORMImplementation/Mappers/DeploymentMapper.cs @@ -28,6 +28,7 @@ public static DeploymentDbModel MapToDbModel(DeploymentEntity deploymentEntity) EnvName = deploymentEntity.EnvName, Buildresult = deploymentEntity.Build.Result.ToEnumMemberAttributeValue(), Created = deploymentEntity.Created.ToUniversalTime(), + CreatedBy = deploymentEntity.CreatedBy, Entity = JsonSerializer.Serialize(deploymentEntity, s_jsonOptions), Build = BuildMapper.MapToDbModel(deploymentEntity.Build, BuildType.Deployment), }; diff --git a/backend/src/Designer/Services/Implementation/AppDevelopmentService.cs b/backend/src/Designer/Services/Implementation/AppDevelopmentService.cs index 33940606ae8..4d0bd99c357 100644 --- a/backend/src/Designer/Services/Implementation/AppDevelopmentService.cs +++ b/backend/src/Designer/Services/Implementation/AppDevelopmentService.cs @@ -9,6 +9,7 @@ using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Models; using Altinn.Studio.DataModeling.Metamodel; +using Altinn.Studio.Designer.Enums; using Altinn.Studio.Designer.Exceptions.AppDevelopment; using Altinn.Studio.Designer.Helpers; using Altinn.Studio.Designer.Infrastructure.GitRepository; @@ -593,5 +594,163 @@ public async Task AddComponentToLayout( layoutArray.Add(component); await SaveFormLayout(editingContext, layoutSetName, layoutName, formLayout, cancellationToken); } + + public async Task<bool> UpdateLayoutReferences(AltinnRepoEditingContext editingContext, List<Reference> referencesToUpdate, CancellationToken cancellationToken) + { + AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository( + editingContext.Org, + editingContext.Repo, + editingContext.Developer); + + LayoutSets layoutSets = await altinnAppGitRepository.GetLayoutSetsFile(cancellationToken); + + return await UpdateLayoutReferences(altinnAppGitRepository, layoutSets.Sets, referencesToUpdate, cancellationToken); + } + + private async Task<bool> UpdateLayoutReferences(AltinnAppGitRepository altinnAppGitRepository, List<LayoutSetConfig> layoutSets, List<Reference> referencesToUpdate, CancellationToken cancellationToken) + { + List<Reference> referencesToDelete = []; + bool hasChanges = false; + + var deletedReferences = referencesToUpdate.Where(item => string.IsNullOrEmpty(item.NewId)).ToList(); + + var deletedLayoutsSetIds = deletedReferences.Where(item => item.Type == ReferenceType.LayoutSet).Select(item => item.Id).ToList(); + var deletedLayouts = deletedReferences.Where(item => item.Type == ReferenceType.Layout).ToList(); + var deletedComponents = deletedReferences.Where(item => item.Type == ReferenceType.Component).ToList(); + + foreach (LayoutSetConfig layoutSet in layoutSets ?? [new() { Id = null }]) + { + bool isLayoutSetDeleted = deletedLayoutsSetIds.Contains(layoutSet.Id); + + Dictionary<string, JsonNode> layouts = await altinnAppGitRepository.GetFormLayouts(layoutSet.Id, cancellationToken); + + var deletedLayoutIdsFromCurrentLayoutSet = deletedLayouts.Where(item => item.LayoutSetName == layoutSet.Id && string.IsNullOrEmpty(item.NewId)).Select(item => item.Id).ToList(); + foreach (KeyValuePair<string, JsonNode> layout in layouts) + { + bool isLayoutDeleted = deletedLayoutIdsFromCurrentLayoutSet.Contains(layout.Key); + bool hasLayoutChanges = false; + + // TODO : https://github.com/Altinn/altinn-studio/issues/14073 + if (layout.Value["data"] is not JsonObject data) + { + continue; + } + + var deletedComponentIdsFromCurrentLayoutSet = deletedComponents.Where(item => item.LayoutSetName == layoutSet.Id && string.IsNullOrEmpty(item.NewId)).Select(item => item.Id).ToList(); + + if (data["layout"] is JsonArray componentList) + { + for (int i = componentList.Count - 1; i >= 0; i--) + { + JsonNode componentNode = componentList[i]; + if (componentNode is not JsonObject component) + { + continue; + } + + string componentId = component["id"]?.GetValue<string>(); + if (string.IsNullOrEmpty(componentId)) + { + continue; + } + + bool isComponentDeleted = deletedComponentIdsFromCurrentLayoutSet.Contains(componentId); + + if (isComponentDeleted) + { + componentList.RemoveAt(i); + hasLayoutChanges = true; + } + + if (isLayoutSetDeleted || isLayoutDeleted || isComponentDeleted) + { + if (!isComponentDeleted) + { + referencesToDelete.Add(new Reference(ReferenceType.Component, layoutSet.Id, componentId)); + } + + continue; + } + + string componentType = component["type"]?.GetValue<string>(); + switch (componentType) + { + case "Subform": + string subformLayoutSet = component["layoutSet"]?.GetValue<string>(); + if (deletedLayoutsSetIds.Contains(subformLayoutSet)) + { + referencesToDelete.Add(new Reference(ReferenceType.Component, layoutSet.Id, componentId)); + componentList.RemoveAt(i); + hasLayoutChanges = true; + } + break; + case "Summary2": + if (component["target"] is JsonObject target) + { + string type = target["type"]?.GetValue<string>(); + string id = target["id"]?.GetValue<string>(); + string taskId = target["taskId"]?.GetValue<string>(); + string layoutSetId = string.IsNullOrEmpty(taskId) ? layoutSet.Id : layoutSets?.FirstOrDefault(item => item.Tasks?.Contains(taskId) ?? false)?.Id; + + if ( + (type == "page" && deletedLayouts.Exists(item => item.LayoutSetName == layoutSetId && item.Id == id)) + || (type == "component" && deletedComponents.Exists(item => item.LayoutSetName == layoutSetId && item.Id == id)) + || deletedLayoutsSetIds.Contains(layoutSetId) + ) + { + referencesToDelete.Add(new Reference(ReferenceType.Component, layoutSet.Id, componentId)); + componentList.RemoveAt(i); + hasLayoutChanges = true; + } + + if (component["overrides"] is JsonArray overrideList) + { + for (int j = overrideList.Count - 1; j >= 0; j--) + { + JsonNode overrideItem = overrideList[j]; + string overrideComponentId = overrideItem["componentId"]?.GetValue<string>(); + if (deletedComponents.Exists(item => item.LayoutSetName == layoutSetId && item.Id == overrideComponentId)) + { + overrideList.RemoveAt(j); + hasLayoutChanges = true; + } + + if (overrideList.Count == 0) + { + component.Remove("overrides"); + } + } + } + } + break; + } + } + } + + if (isLayoutSetDeleted || isLayoutDeleted) + { + if (!isLayoutDeleted) + { + referencesToDelete.Add(new Reference(ReferenceType.Layout, layoutSet.Id, layout.Key)); + } + + continue; + } + + if (hasLayoutChanges) + { + await altinnAppGitRepository.SaveLayout(layoutSet.Id, $"{layout.Key}.json", layout.Value, cancellationToken); + hasChanges = true; + } + } + } + + if (referencesToDelete.Count > 0) + { + hasChanges |= await UpdateLayoutReferences(altinnAppGitRepository, layoutSets, referencesToDelete, cancellationToken); + } + + return hasChanges; + } } } diff --git a/backend/src/Designer/Services/Interfaces/IAppDevelopmentService.cs b/backend/src/Designer/Services/Interfaces/IAppDevelopmentService.cs index e175fba77d0..11e656b1ec0 100644 --- a/backend/src/Designer/Services/Interfaces/IAppDevelopmentService.cs +++ b/backend/src/Designer/Services/Interfaces/IAppDevelopmentService.cs @@ -213,5 +213,13 @@ public Task<ModelMetadata> GetModelMetadata( /// <param name="component">The component to add.</param> /// <param name="cancellationToken">An <see cref="CancellationToken"/> that observes if operation is cancelled.</param> public Task AddComponentToLayout(AltinnRepoEditingContext altinnRepoEditingContext, string layoutSetName, string layoutName, object component, CancellationToken cancellationToken = default); + + /// <summary> + /// Update layout references + /// </summary> + /// <param name="altinnRepoEditingContext">An <see cref="AltinnRepoEditingContext"/>.</param> + /// <param name="referencesToUpdate">The references to update.</param> + /// <param name="cancellationToken">An <see cref="CancellationToken"/> that observes if operation is cancelled.</param> + public Task<bool> UpdateLayoutReferences(AltinnRepoEditingContext altinnRepoEditingContext, List<Reference> referencesToUpdate, CancellationToken cancellationToken); } } diff --git a/backend/tests/DataModeling.Tests/AltinnJsonSchemaValidationTests.cs b/backend/tests/DataModeling.Tests/AltinnJsonSchemaValidationTests.cs index 7548fcd274e..02c3fdfd49d 100644 --- a/backend/tests/DataModeling.Tests/AltinnJsonSchemaValidationTests.cs +++ b/backend/tests/DataModeling.Tests/AltinnJsonSchemaValidationTests.cs @@ -2,7 +2,6 @@ using Altinn.Studio.DataModeling.Validator.Json; using DataModeling.Tests.BaseClasses; using DataModeling.Tests.TestDataClasses; -using FluentAssertions; using Json.Pointer; using Xunit; @@ -17,8 +16,9 @@ public class AltinnJsonSchemaValidationTests : SchemaConversionTestsBase<AltinnJ public void ValidJsonSchema_ShouldNotHave_ValidationIssues(string jsonSchemaPath) { When.JsonSchemaLoaded(jsonSchemaPath) - .And.LoadedJsonSchemaValidated() - .Then.ValidationResult.IsValid.Should().BeTrue(); + .And.LoadedJsonSchemaValidated(); + + Assert.True(ValidationResult.IsValid); } [Theory] @@ -26,14 +26,15 @@ public void ValidJsonSchema_ShouldNotHave_ValidationIssues(string jsonSchemaPath public void InvalidJsonSchema_ShouldHave_ValidationIssues(string jsonSchemaPath, params Tuple<string, string>[] expectedValidationIssues) { When.JsonSchemaLoaded(jsonSchemaPath) - .And.LoadedJsonSchemaValidated() - .Then.ValidationResult.IsValid.Should().BeFalse(); + .And.LoadedJsonSchemaValidated(); + + Assert.False(ValidationResult.IsValid); - And.ValidationResult.ValidationIssues.Should().HaveCount(expectedValidationIssues.Length); + Assert.Equal(ValidationResult.ValidationIssues.Count, expectedValidationIssues.Length); foreach ((string expectedPointer, string expectedCode) in expectedValidationIssues) { - ValidationResult.ValidationIssues.Should().Contain(x => x.ErrorCode == expectedCode && JsonPointer.Parse(x.IssuePointer) == JsonPointer.Parse(expectedPointer)); + Assert.Contains(ValidationResult.ValidationIssues, x => x.ErrorCode == expectedCode && JsonPointer.Parse(x.IssuePointer) == JsonPointer.Parse(expectedPointer)); } } diff --git a/backend/tests/DataModeling.Tests/Assertions/TypeAssertions.cs b/backend/tests/DataModeling.Tests/Assertions/TypeAssertions.cs index bcc545a2a43..e0263c3cd6a 100644 --- a/backend/tests/DataModeling.Tests/Assertions/TypeAssertions.cs +++ b/backend/tests/DataModeling.Tests/Assertions/TypeAssertions.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using Altinn.Studio.DataModeling.Converter.Csharp; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Assertions; @@ -16,15 +15,15 @@ public static void IsEquivalentTo(Type expected, Type actual) { if (expected.IsPrimitive || expected == typeof(string) || expected == typeof(DateTime) || expected == typeof(decimal)) { - expected.Should().Be(actual); + Assert.Equal(expected, actual); return; } - expected.Name.Should().Be(actual.Name); - expected.Namespace.Should().Be(actual.Namespace); - expected.IsArray.Should().Be(actual.IsArray); - expected.IsClass.Should().Be(actual.IsClass); - expected.IsGenericType.Should().Be(actual.IsGenericType); + Assert.Equal(expected.Name, actual.Name); + Assert.Equal(expected.Namespace, actual.Namespace); + Assert.Equal(expected.IsArray, actual.IsArray); + Assert.Equal(expected.IsClass, actual.IsClass); + Assert.Equal(expected.IsGenericType, actual.IsGenericType); if (expected.IsGenericType) { foreach (var expectedArg in expected.GenericTypeArguments) @@ -47,7 +46,7 @@ public static void IsEquivalentTo(Type expected, Type actual) private static void IsEquivalentTo(IReadOnlyCollection<FieldInfo> expected, IReadOnlyCollection<FieldInfo> actual) { - expected.Count.Should().Be(actual.Count); + Assert.Equal(expected.Count, actual.Count); foreach (var expectedItem in expected) { var actualItem = actual.Single(x => x.Name == expectedItem.Name); @@ -57,16 +56,16 @@ private static void IsEquivalentTo(IReadOnlyCollection<FieldInfo> expected, IRea private static void IsEquivalentTo(FieldInfo expected, FieldInfo actual) { - expected.Name.Should().Be(actual.Name); + Assert.Equal(expected.Name, actual.Name); IsEquivalentTo(expected.FieldType, actual.FieldType); IsEquivalentTo(expected.CustomAttributes, actual.CustomAttributes); - expected.IsPrivate.Should().Be(actual.IsPrivate); - expected.IsPublic.Should().Be(actual.IsPublic); + Assert.Equal(expected.IsPrivate, actual.IsPrivate); + Assert.Equal(expected.IsPublic, actual.IsPublic); } private static void IsEquivalentTo(IReadOnlyCollection<PropertyInfo> expected, IReadOnlyCollection<PropertyInfo> actual) { - expected.Count.Should().Be(actual.Count); + Assert.Equal(expected.Count, actual.Count); foreach (var expectedItem in expected) { var actualItem = actual.Single(x => x.Name == expectedItem.Name); @@ -76,7 +75,7 @@ private static void IsEquivalentTo(IReadOnlyCollection<PropertyInfo> expected, I private static void IsEquivalentTo(PropertyInfo expected, PropertyInfo actual) { - expected.Name.Should().Be(actual.Name); + Assert.Equal(expected.Name, actual.Name); IsEquivalentTo(expected.PropertyType, actual.PropertyType); IsEquivalentTo(expected.CustomAttributes, actual.CustomAttributes); } @@ -85,17 +84,17 @@ private static void IsEquivalentTo(IEnumerable<CustomAttributeData> expected, IE { var expectedStrings = expected.Select(e => e.ToString()); var actualStrings = actual.Select(a => a.ToString()); - expectedStrings.Should().BeEquivalentTo(actualStrings); + Assert.True(expectedStrings.SequenceEqual(actualStrings)); } private static void IsEquivalentTo(TypeAttributes expected, TypeAttributes actual) { - expected.Should().Be(actual); + Assert.Equal(expected, actual); } private static void IsEquivalentTo(IReadOnlyCollection<MethodInfo> expected, IReadOnlyCollection<MethodInfo> actual) { - expected.Count.Should().Be(actual.Count); + Assert.Equal(expected.Count, actual.Count); foreach (var expectedItem in expected) { var actualItem = actual.Single(x => x.Name == expectedItem.Name); @@ -105,17 +104,17 @@ private static void IsEquivalentTo(IReadOnlyCollection<MethodInfo> expected, IRe private static void IsEquivalentTo(MethodInfo expected, MethodInfo actual) { - expected.Name.Should().Be(actual.Name); + Assert.Equal(expected.Name, actual.Name); IsEquivalentTo(expected.ReturnType, actual.ReturnType); - actual.IsPublic.Should().Be(expected.IsPublic); + Assert.Equal(expected.IsPublic, actual.IsPublic); } public static void PropertyShouldContainCustomAnnotationAndHaveTypeType(Type type, string propertyName, string propertyType, string expectedAnnotationString) { - var property = type.Properties().Single(x => x.Name == propertyName); + var property = type.GetProperties().Single(x => x.Name == propertyName); var simpleCompiledAssembly = Compiler.CompileToAssembly(DynamicAnnotationClassString(propertyName, propertyType, expectedAnnotationString)); - var expectedProperty = simpleCompiledAssembly.Types() - .First(x => x.Name == "DynamicAnnotationClass").Properties().Single(); + var expectedProperty = simpleCompiledAssembly.GetTypes() + .First(x => x.Name == "DynamicAnnotationClass").GetProperties().Single(); IsEquivalentTo(expectedProperty.PropertyType, property.PropertyType); var expectedAnnotation = expectedProperty.CustomAttributes.Single(); Assert.Single(property.CustomAttributes, x => x.ToString() == expectedAnnotation.ToString()); diff --git a/backend/tests/DataModeling.Tests/CsharpEnd2EndGenerationTests.cs b/backend/tests/DataModeling.Tests/CsharpEnd2EndGenerationTests.cs index ed8d1e6bdf1..4e649677a88 100644 --- a/backend/tests/DataModeling.Tests/CsharpEnd2EndGenerationTests.cs +++ b/backend/tests/DataModeling.Tests/CsharpEnd2EndGenerationTests.cs @@ -4,7 +4,6 @@ using DataModeling.Tests.Assertions; using DataModeling.Tests.BaseClasses; using DataModeling.Tests.TestDataClasses; -using FluentAssertions; using SharedResources.Tests; using Xunit; using Xunit.Abstractions; @@ -28,9 +27,9 @@ public void Convert_FromXsd_Should_EqualExpected(string xsdSchemaPath, string ex .When.LoadedXsdSchemaConvertedToJsonSchema() .And.ConvertedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then - .CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); And.GeneratedClassesShouldBeEquivalentToExpected(expectedCsharpClassPath); } @@ -44,8 +43,9 @@ public void Convert_CSharpClass_ShouldContainRestriction(string xsdSchemaPath, s .When.LoadedXsdSchemaConvertedToJsonSchema() .And.ConvertedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then.CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); And.PropertyShouldHaveDefinedTypeAndContainAnnotation("Root", propertyName, expectedPropertyType, restrictionString); } @@ -57,12 +57,14 @@ public void JsonSchemaShouldConvertToXsdAndCSharp(string jsonSchemaPath, params Given.That.JsonSchemaLoaded(jsonSchemaPath) .When.LoadedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then.CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); And.ClassesShouldBeGenerated(typesCreated) - .And.When.LoadedJsonSchemaConvertedToXsdSchema() - .Then.ConvertedXsdSchema.Should().NotBeNull(); + .And.When.LoadedJsonSchemaConvertedToXsdSchema(); + + Assert.NotNull(ConvertedXsdSchema); } [Theory] @@ -72,8 +74,9 @@ public void JsonSchemaWithStringFieldInUriFormatShouldConvertToCSharp(string jso Given.That.JsonSchemaLoaded(jsonSchemaPath) .When.LoadedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then.CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); } private void GeneratedClassesShouldBeEquivalentToExpected(string expectedCsharpClassPath, bool overwriteExpected = false) @@ -94,15 +97,16 @@ private void GeneratedClassesShouldBeEquivalentToExpected(string expectedCsharpC var expectedAssembly = Compiler.CompileToAssembly(expectedClasses); // Compare root types. - var newType = CompiledAssembly.Types().Single(type => type.CustomAttributes.Any(att => att.AttributeType == typeof(XmlRootAttribute))); + var newType = CompiledAssembly.GetTypes().Single(type => type.CustomAttributes.Any(att => att.AttributeType == typeof(XmlRootAttribute))); var oldType = expectedAssembly.GetType(newType.FullName); - oldType.Should().NotBeNull(); + Assert.NotNull(oldType); + TypeAssertions.IsEquivalentTo(oldType, newType); } private void PropertyShouldHaveDefinedTypeAndContainAnnotation(string className, string propertyName, string propertyType, string annotationString) { - var type = CompiledAssembly.Types().Single(type => type.Name == className); + var type = CompiledAssembly.GetTypes().Single(type => type.Name == className); TypeAssertions.PropertyShouldContainCustomAnnotationAndHaveTypeType(type, propertyName, propertyType, annotationString); } @@ -111,8 +115,9 @@ private CsharpEnd2EndGenerationTests ClassesShouldBeGenerated(string[] className { foreach (string className in classNames) { - var type = CompiledAssembly.Types().Single(type => type.Name == className); - type.Should().NotBeNull(); + var type = CompiledAssembly.GetTypes().Single(type => type.Name == className); + + Assert.NotNull(type); } return this; } diff --git a/backend/tests/DataModeling.Tests/DataModeling.Tests.csproj b/backend/tests/DataModeling.Tests/DataModeling.Tests.csproj index a1dbd2fb83b..eeded94d458 100644 --- a/backend/tests/DataModeling.Tests/DataModeling.Tests.csproj +++ b/backend/tests/DataModeling.Tests/DataModeling.Tests.csproj @@ -5,7 +5,6 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="FluentAssertions" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" /> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Moq" /> diff --git a/backend/tests/DataModeling.Tests/DataValidationWithModelPopulatingTests.cs b/backend/tests/DataModeling.Tests/DataValidationWithModelPopulatingTests.cs index ab504bdc3c9..e3e119223a1 100644 --- a/backend/tests/DataModeling.Tests/DataValidationWithModelPopulatingTests.cs +++ b/backend/tests/DataModeling.Tests/DataValidationWithModelPopulatingTests.cs @@ -11,7 +11,6 @@ using System.Xml.Serialization; using DataModeling.Tests.BaseClasses; using DataModeling.Tests.Utils; -using FluentAssertions; using Xunit; using Xunit.Abstractions; @@ -44,8 +43,10 @@ public void Data_ShouldValidateAgainstSchemas(string xsdSchemaPath) .When.LoadedXsdSchemaConvertedToJsonSchema() .And.ConvertedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then.CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); + When.RepresentingTypeFromLoadedFromAssembly() .And.RandomRepresentingObjectGenerated() @@ -56,7 +57,7 @@ public void Data_ShouldValidateAgainstSchemas(string xsdSchemaPath) private DataValidationWithModelPopulatingTests RepresentingTypeFromLoadedFromAssembly() { - RepresentingType = CompiledAssembly.Types().Single(type => type.CustomAttributes.Any(att => att.AttributeType == typeof(XmlRootAttribute))); + RepresentingType = CompiledAssembly.GetTypes().Single(type => type.CustomAttributes.Any(att => att.AttributeType == typeof(XmlRootAttribute))); return this; } @@ -69,7 +70,8 @@ private DataValidationWithModelPopulatingTests RandomRepresentingObjectGenerated private DataValidationWithModelPopulatingTests RepresentingObject_ShouldBeValid() { var isValid = Validator.TryValidateObject(RandomRepresentingObject, new ValidationContext(RandomRepresentingObject), null, true); - isValid.Should().BeTrue(); + + Assert.True(isValid); return this; } @@ -101,7 +103,8 @@ void ValidationEventHandler(object sender, ValidationEventArgs e) document.Schemas.Add(LoadedXsdSchema); ValidationEventHandler eventHandler = ValidationEventHandler; document.Validate(eventHandler); - isValid.Should().BeTrue(); + + Assert.True(isValid); return this; } @@ -114,6 +117,7 @@ private void RepresentingObject_ShouldValidateAgainstJsonSchema() }); var jsonNode = JsonNode.Parse(json); var validationResults = ConvertedJsonSchema.Evaluate(jsonNode); - validationResults.IsValid.Should().BeTrue(); + + Assert.True(validationResults.IsValid); } } diff --git a/backend/tests/DataModeling.Tests/Json/Formats/CustomFormatsTests.cs b/backend/tests/DataModeling.Tests/Json/Formats/CustomFormatsTests.cs index 0e36caf4d9c..c8f638024b0 100644 --- a/backend/tests/DataModeling.Tests/Json/Formats/CustomFormatsTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Formats/CustomFormatsTests.cs @@ -4,7 +4,6 @@ using System.Reflection; using System.Text.Json.Nodes; using Altinn.Studio.DataModeling.Json.Formats; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Formats; @@ -34,6 +33,7 @@ public void TestDateFormat(string property, bool expected) objects.Add(node[property]); var result = checkDateMethod.Invoke(null, objects.ToArray()); - expected.Should().Be((bool)result); + + Assert.Equal(expected, (bool)result); } } diff --git a/backend/tests/DataModeling.Tests/Json/JsonSchemaNormalizerTests.cs b/backend/tests/DataModeling.Tests/Json/JsonSchemaNormalizerTests.cs index ec82f1a9d16..356c691442d 100644 --- a/backend/tests/DataModeling.Tests/Json/JsonSchemaNormalizerTests.cs +++ b/backend/tests/DataModeling.Tests/Json/JsonSchemaNormalizerTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Altinn.Studio.DataModeling.Json; using Altinn.Studio.DataModeling.Json.Keywords; -using FluentAssertions; using SharedResources.Tests; using Xunit; @@ -50,7 +49,7 @@ public Task Normalize_NoNormalization_ShouldEqualSourceSchema(string jsonSchemaT var normalizedJsonSchema = jsonSchemaNormalizer.Normalize(jsonSchema); var normalizedJsonSchemaText = JsonSerializer.Serialize(normalizedJsonSchema, new JsonSerializerOptions() { Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Latin1Supplement) }); - normalizedJsonSchemaText.Should().BeEquivalentTo(jsonSchemaText); + Assert.Equal(jsonSchemaText, normalizedJsonSchemaText); return Task.CompletedTask; } @@ -78,7 +77,7 @@ public Task Normalize_WithNormalization_ShouldRemoveSingleAllOfs(string jsonSche var json = JsonSerializer.Serialize(normalizedJsonSchema, new JsonSerializerOptions { WriteIndented = true }); - normalizedJsonSchemaText.Should().BeEquivalentTo(expectedNormalizedJsonSchemaText); + Assert.Equal(expectedNormalizedJsonSchemaText, normalizedJsonSchemaText); return Task.CompletedTask; } } diff --git a/backend/tests/DataModeling.Tests/Json/JsonSchemaSeresAnalyzerTests.cs b/backend/tests/DataModeling.Tests/Json/JsonSchemaSeresAnalyzerTests.cs index 24589e7f625..e6e9615d7ec 100644 --- a/backend/tests/DataModeling.Tests/Json/JsonSchemaSeresAnalyzerTests.cs +++ b/backend/tests/DataModeling.Tests/Json/JsonSchemaSeresAnalyzerTests.cs @@ -3,7 +3,6 @@ using Altinn.Studio.DataModeling.Converter.Json.Strategy; using Altinn.Studio.DataModeling.Json; using Altinn.Studio.DataModeling.Json.Keywords; -using FluentAssertions; using Json.Pointer; using SharedResources.Tests; using Xunit; @@ -40,7 +39,7 @@ public void IsValidComplexType_ComplexType_ShouldReturnTrue(string path, string var results = analyzer.AnalyzeSchema(schema); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().Contain(CompatibleXsdType.ComplexType); + Assert.Contains(CompatibleXsdType.ComplexType, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); } [Theory] @@ -58,10 +57,10 @@ public void IsValidComplexContentExtension_ComplexContentExtention_ShouldReturnT var results = analyzer.AnalyzeSchema(schema); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().Contain(CompatibleXsdType.ComplexContent); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().Contain(CompatibleXsdType.ComplexContentExtension); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().NotContain(CompatibleXsdType.SimpleContentExtension); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().NotContain(CompatibleXsdType.SimpleContentRestriction); + Assert.Contains(CompatibleXsdType.ComplexContent, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); + Assert.Contains(CompatibleXsdType.ComplexContentExtension, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); + Assert.DoesNotContain(CompatibleXsdType.SimpleContentExtension, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); + Assert.DoesNotContain(CompatibleXsdType.SimpleContentRestriction, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); } [Theory] @@ -75,8 +74,8 @@ public void IsValidComplexContentExtension_NotComplexContentExtention_ShouldRetu var results = analyzer.AnalyzeSchema(schema); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().NotContain(CompatibleXsdType.ComplexContent); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().NotContain(CompatibleXsdType.ComplexContentExtension); + Assert.DoesNotContain(CompatibleXsdType.ComplexContent, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); + Assert.DoesNotContain(CompatibleXsdType.ComplexContentExtension, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); } [Theory] @@ -93,8 +92,8 @@ public void IsValidAttribute_Attribute_ShouldReturnTrue(string path, string json var results = analyzer.AnalyzeSchema(schema); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().Contain(CompatibleXsdType.SimpleType); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().Contain(CompatibleXsdType.Attribute); + Assert.Contains(CompatibleXsdType.SimpleType, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); + Assert.Contains(CompatibleXsdType.Attribute, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); } [Theory] @@ -109,7 +108,7 @@ public void IsValidNillableAttribute_NillableAttribute_ShouldReturnTrue(string p var results = analyzer.AnalyzeSchema(schema); - results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer)).Should().Contain(CompatibleXsdType.Nillable); + Assert.Contains(CompatibleXsdType.Nillable, results.GetCompatibleTypes(JsonPointer.Parse(jsonPointer))); } } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/BaseClasses/ConverterTestBase.cs b/backend/tests/DataModeling.Tests/Json/Keywords/BaseClasses/ConverterTestBase.cs index 43c7a70b38f..9ad9a18b8b6 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/BaseClasses/ConverterTestBase.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/BaseClasses/ConverterTestBase.cs @@ -3,9 +3,9 @@ using System.Text.Unicode; using Altinn.Studio.DataModeling.Json.Keywords; using Altinn.Studio.DataModeling.Utils; -using FluentAssertions; using Json.Schema; using SharedResources.Tests; +using Xunit; namespace DataModeling.Tests.Json.Keywords.BaseClasses; @@ -51,7 +51,14 @@ protected TTestType KeywordReadFromSchema() protected TTestType SerializedKeywordShouldBe(string json) { - KeywordNodeJson.Should().Be(json); + Assert.Equal(KeywordNodeJson, json); return this as TTestType; } + + protected TTestType KeywordShouldNotBeNull() + { + Assert.NotNull(Keyword); + return this as TTestType; + } + } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatExclusiveMaximumKeywordJsonConverterConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatExclusiveMaximumKeywordJsonConverterConverterTests.cs index b4bc7fca40a..467a96c93ab 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatExclusiveMaximumKeywordJsonConverterConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatExclusiveMaximumKeywordJsonConverterConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.FormatRange.Converter; @@ -31,9 +30,11 @@ public void Read_ValidJson_FromSchema(string value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); + + Assert.Equal(Keyword.Value, value); + - And.Keyword.Value.Should().Be(value); } [Theory] @@ -47,6 +48,7 @@ public void Read_InvalidJson_ShouldThrow(string value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected string"); + + Assert.Equal("Expected string", ex.Message); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatExclusiveMinimumKeywordJsonConverterConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatExclusiveMinimumKeywordJsonConverterConverterTests.cs index f3761d1e0e3..21dc818731b 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatExclusiveMinimumKeywordJsonConverterConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatExclusiveMinimumKeywordJsonConverterConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.FormatRange.Converter; @@ -31,9 +30,9 @@ public void Read_ValidJson_FromSchema(string value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] @@ -47,6 +46,7 @@ public void Read_InvalidJson_ShouldThrow(string value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected string"); + + Assert.Equal("Expected string", ex.Message); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatMaximumKeywordJsonConverterConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatMaximumKeywordJsonConverterConverterTests.cs index 0699ca8ef1c..d541c075b41 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatMaximumKeywordJsonConverterConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatMaximumKeywordJsonConverterConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.FormatRange.Converter; @@ -31,9 +30,9 @@ public void Read_ValidJson_FromSchema(string value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] @@ -47,6 +46,7 @@ public void Read_InvalidJson_ShouldThrow(string value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected string"); + + Assert.Equal("Expected string", ex.Message); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatMinimumKeywordJsonConverterConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatMinimumKeywordJsonConverterConverterTests.cs index c83b3d5471d..a4b62b23606 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatMinimumKeywordJsonConverterConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Converter/FormatMinimumKeywordJsonConverterConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.FormatRange.Converter; @@ -31,9 +30,9 @@ public void Read_ValidJson_FromSchema(string value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] @@ -47,6 +46,7 @@ public void Read_InvalidJson_ShouldThrow(string value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected string"); + + Assert.Equal("Expected string", ex.Message); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatExclusiveMaximumKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatExclusiveMaximumKeywordTests.cs index 92f2469163b..5e1d1072879 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatExclusiveMaximumKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatExclusiveMaximumKeywordTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.FormatRange.Keyword; @@ -14,7 +13,7 @@ public class FormatExclusiveMaximumKeywordTests : ValueKeywordTestsBase<FormatEx public void CreatedKeyword_ShouldHaveValue(string value) { Keyword = new FormatExclusiveMaximumKeyword(value); - Keyword.Value.Should().Be(value); + Assert.Equal(value, Keyword.Value); } [Theory] @@ -36,6 +35,7 @@ public void GetHashCode_ShouldBe_As_Value(string value) { var expectedHashCode = value.GetHashCode(); Given.That.KeywordCreatedWithValue(value); - expectedHashCode.GetHashCode().Should().Be(Keyword.GetHashCode()); + + Assert.Equal(expectedHashCode, Keyword.GetHashCode()); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatExclusiveMinimumKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatExclusiveMinimumKeywordTests.cs index cee597de5d6..0114760fcb6 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatExclusiveMinimumKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatExclusiveMinimumKeywordTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.FormatRange.Keyword; @@ -14,7 +13,7 @@ public class FormatExclusiveMinimumKeywordTests : ValueKeywordTestsBase<FormatEx public void CreatedKeyword_ShouldHaveValue(string value) { Keyword = new FormatExclusiveMinimumKeyword(value); - Keyword.Value.Should().Be(value); + Assert.Equal(value, Keyword.Value); } [Theory] @@ -36,6 +35,6 @@ public void GetHashCode_ShouldBe_As_Value(string value) { var expectedHashCode = value.GetHashCode(); Given.That.KeywordCreatedWithValue(value); - expectedHashCode.GetHashCode().Should().Be(Keyword.GetHashCode()); + Assert.Equal(expectedHashCode, Keyword.GetHashCode()); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatMaximumKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatMaximumKeywordTests.cs index 6d44aac49e3..0fb146fb21e 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatMaximumKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatMaximumKeywordTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.FormatRange.Keyword; @@ -14,7 +13,7 @@ public class FormatMaximumKeywordTests : ValueKeywordTestsBase<FormatMaximumKeyw public void CreatedKeyword_ShouldHaveValue(string value) { Given.That.KeywordCreatedWithValue(value); - Keyword.Value.Should().Be(value); + Assert.Equal(value, Keyword.Value); } [Theory] @@ -36,6 +35,6 @@ public void GetHashCode_ShouldBe_As_Value(string value) { var expectedHashCode = value.GetHashCode(); Given.That.KeywordCreatedWithValue(value); - expectedHashCode.GetHashCode().Should().Be(Keyword.GetHashCode()); + Assert.Equal(expectedHashCode, Keyword.GetHashCode()); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatMinimumKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatMinimumKeywordTests.cs index d354f0eab0d..091de9048d4 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatMinimumKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/FormatRange/Keyword/FormatMinimumKeywordTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.FormatRange.Keyword; @@ -14,7 +13,7 @@ public class FormatMinimumKeywordTests : ValueKeywordTestsBase<FormatMinimumKeyw public void CreatedKeyword_ShouldHaveValue(string value) { Keyword = new FormatMinimumKeyword(value); - Keyword.Value.Should().Be(value); + Assert.Equal(value, Keyword.Value); } [Theory] @@ -36,6 +35,6 @@ public void GetHashCode_ShouldBe_As_Value(string value) { var expectedHashCode = value.GetHashCode(); Given.That.KeywordCreatedWithValue(value); - expectedHashCode.GetHashCode().Should().Be(Keyword.GetHashCode()); + Assert.Equal(expectedHashCode, Keyword.GetHashCode()); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Converter/XsdMaxOccursKeywordJsonConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Converter/XsdMaxOccursKeywordJsonConverterTests.cs index 2df179e3e19..264748e4466 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Converter/XsdMaxOccursKeywordJsonConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Converter/XsdMaxOccursKeywordJsonConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.OccursKeywords.Converter @@ -24,9 +23,9 @@ public void Read_ValidJson_FromSchema(string value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] @@ -51,7 +50,8 @@ public void Read_InvalidJson_ShouldThrow(string value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected string"); + + Assert.Equal("Expected string", ex.Message); } } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Converter/XsdMinOccursKeywordJsonConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Converter/XsdMinOccursKeywordJsonConverterTests.cs index 988fd55c709..b65b502519d 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Converter/XsdMinOccursKeywordJsonConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Converter/XsdMinOccursKeywordJsonConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.OccursKeywords.Converter @@ -24,9 +23,9 @@ public void Read_ValidJson_FromSchema(int value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] @@ -51,7 +50,8 @@ public void Read_InvalidJson_ShouldThrow(int value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected number"); + + Assert.Equal("Expected number", ex.Message); } } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Keyword/XsdMaxOccursKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Keyword/XsdMaxOccursKeywordTests.cs index 421e04e3525..6ac8bf0f231 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Keyword/XsdMaxOccursKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Keyword/XsdMaxOccursKeywordTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.OccursKeywords.Keyword; @@ -17,7 +16,7 @@ public class XsdMaxOccursKeywordTests : ValueKeywordTestsBase<XsdMaxOccursKeywor public void CreatedKeyword_ShouldHaveValue(string value) { Keyword = new XsdMaxOccursKeyword(value); - Keyword.Value.Should().Be(value); + Assert.Equal(value, Keyword.Value); } [Theory] @@ -45,6 +44,7 @@ public void GetHashCode_ShouldBe_As_Value(string value) { var expectedHashCode = value.GetHashCode(); Given.That.KeywordCreatedWithValue(value); - expectedHashCode.GetHashCode().Should().Be(Keyword.GetHashCode()); + + Assert.Equal(expectedHashCode, Keyword.GetHashCode()); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Keyword/XsdMinOccursKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Keyword/XsdMinOccursKeywordTests.cs index c88b15b9d9e..0303d68d76b 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Keyword/XsdMinOccursKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/OccursKeywords/Keyword/XsdMinOccursKeywordTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords.OccursKeywords.Keyword; @@ -16,7 +15,7 @@ public class XsdMinOccursKeywordTests : ValueKeywordTestsBase<XsdMinOccursKeywor public void CreatedKeyword_ShouldHaveValue(int value) { Keyword = new XsdMinOccursKeyword(value); - Keyword.Value.Should().Be(value); + Assert.Equal(value, Keyword.Value); } [Theory] @@ -42,6 +41,6 @@ public void GetHashCode_ShouldBe_As_Value(int value) { var expectedHashCode = value.GetHashCode(); Given.That.KeywordCreatedWithValue(value); - expectedHashCode.GetHashCode().Should().Be(Keyword.GetHashCode()); + Assert.Equal(expectedHashCode, Keyword.GetHashCode()); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/XsdNillableKeywordJsonConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/XsdNillableKeywordJsonConverterTests.cs index d5f9593b6bb..fdd19b2e6fc 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/XsdNillableKeywordJsonConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/XsdNillableKeywordJsonConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords @@ -23,9 +22,9 @@ public void Read_ValidJson_FromSchema(bool value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] @@ -49,7 +48,8 @@ public void Read_InvalidJson_ShouldThrow(bool value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected boolean"); + + Assert.Equal("Expected boolean", ex.Message); } } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/XsdRootElementKeywordJsonConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/XsdRootElementKeywordJsonConverterTests.cs index ab16df968df..4d91c51322a 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/XsdRootElementKeywordJsonConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/XsdRootElementKeywordJsonConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords @@ -23,9 +22,9 @@ public void Read_ValidJson_FromSchema(string value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] @@ -49,7 +48,7 @@ public void Read_InvalidJson_ShouldThrow(string value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected string"); + Assert.Equal("Expected string", ex.Message); } } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/XsdRootElementKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/XsdRootElementKeywordTests.cs index 14c15cff8f7..105a6197a91 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/XsdRootElementKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/XsdRootElementKeywordTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords; @@ -29,6 +28,7 @@ public void GetHashCode_ShouldBe_As_Value(string value) { var expectedHashCode = value.GetHashCode(); Given.That.KeywordCreatedWithValue(value); - expectedHashCode.GetHashCode().Should().Be(Keyword.GetHashCode()); + + Assert.Equal(expectedHashCode, Keyword.GetHashCode()); } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/XsdTextKeywordJsonConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/XsdTextKeywordJsonConverterTests.cs index 5bd6076fb85..7e3c7078e19 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/XsdTextKeywordJsonConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/XsdTextKeywordJsonConverterTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords @@ -22,9 +21,9 @@ public void Read_ValidJson_FromSchema(bool value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/XsdTextKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/XsdTextKeywordTests.cs index 2420b803c37..40a10353717 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/XsdTextKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/XsdTextKeywordTests.cs @@ -1,6 +1,5 @@ using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords; @@ -13,7 +12,7 @@ public class XsdTextKeywordTests : ValueKeywordTestsBase<XsdTextKeywordTests, Xs public void DefaultValue_ShouldBe_False() { Keyword = new XsdTextKeyword(); - Keyword.Value.Should().Be(false); + Assert.False(Keyword.Value); } [Theory] diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/XsdTotalDigitsKeywordJsonConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/XsdTotalDigitsKeywordJsonConverterTests.cs index 0fbb54dd422..8457af6764c 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/XsdTotalDigitsKeywordJsonConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/XsdTotalDigitsKeywordJsonConverterTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Json.Keywords @@ -23,9 +22,9 @@ public void Read_ValidJson_FromSchema(uint value) Given.That.JsonSchemaLoaded(jsonSchema) .When.KeywordReadFromSchema() - .Then.Keyword.Should().NotBeNull(); + .Then.KeywordShouldNotBeNull(); - And.Keyword.Value.Should().Be(value); + Assert.Equal(Keyword.Value, value); } [Theory] @@ -49,7 +48,7 @@ public void Read_InvalidJson_ShouldThrow(uint value) var ex = Assert.Throws<JsonException>(() => Given.That.JsonSchemaLoaded(jsonSchema)); - ex.Message.Should().Be("Expected number"); + Assert.Equal("Expected number", ex.Message); } } } diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/XsdTotalDigitsKeywordTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/XsdTotalDigitsKeywordTests.cs index f22337a7e24..18e66581345 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/XsdTotalDigitsKeywordTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/XsdTotalDigitsKeywordTests.cs @@ -1,7 +1,6 @@ using System.Text.Json.Nodes; using Altinn.Studio.DataModeling.Json.Keywords; using DataModeling.Tests.Json.Keywords.BaseClasses; -using FluentAssertions; using Json.Schema; using Xunit; @@ -19,7 +18,7 @@ public class XsdTotalDigitsKeywordTests : ValueKeywordTestsBase<XsdTotalDigitsKe public void CreatedKeyword_ShouldHaveValue(uint value) { Keyword = new XsdTotalDigitsKeyword(value); - Keyword.Value.Should().Be(value); + Assert.Equal(value, Keyword.Value); } [Theory] @@ -43,7 +42,7 @@ public void GetHashCode_ShouldBe_As_Value(uint value) { var expectedHashCode = value.GetHashCode(); Given.That.KeywordCreatedWithValue(value); - expectedHashCode.GetHashCode().Should().Be(Keyword.GetHashCode()); + Assert.Equal(expectedHashCode, Keyword.GetHashCode()); } [Theory] @@ -57,7 +56,7 @@ public void Keyword_ShouldValidate(uint totalDigitsValue, string jsonDataValue, var schema = JsonSchema.FromText(TotalDigitsSchema(totalDigitsValue)); var node = JsonNode.Parse(TotalDigitsJson(jsonDataValue)); var validationResults = schema.Evaluate(node, new EvaluationOptions() { ProcessCustomKeywords = true }); - shouldBeValid.Should().Be(validationResults.IsValid); + Assert.Equal(shouldBeValid, validationResults.IsValid); } private static string TotalDigitsSchema(uint value) => @$" diff --git a/backend/tests/DataModeling.Tests/Json/Keywords/XsdUnhandledEnumAttributesKeywordJsonConverterTests.cs b/backend/tests/DataModeling.Tests/Json/Keywords/XsdUnhandledEnumAttributesKeywordJsonConverterTests.cs index 33ed6704ba9..b0434e520d4 100644 --- a/backend/tests/DataModeling.Tests/Json/Keywords/XsdUnhandledEnumAttributesKeywordJsonConverterTests.cs +++ b/backend/tests/DataModeling.Tests/Json/Keywords/XsdUnhandledEnumAttributesKeywordJsonConverterTests.cs @@ -4,7 +4,6 @@ using System.Text; using System.Text.Json; using Altinn.Studio.DataModeling.Json.Keywords; -using FluentAssertions; using Xunit; using static Altinn.Studio.DataModeling.Json.Keywords.XsdUnhandledEnumAttributesKeyword; @@ -24,10 +23,10 @@ public void Read_ValidJson_ShouldReadFromJson() var xsdUnhandledEnumAttributesKeyword = keywordConverter.Read(ref jsonReader, typeof(XsdUnhandledAttributesKeyword), new System.Text.Json.JsonSerializerOptions()); // Assert - xsdUnhandledEnumAttributesKeyword.Properties.Should().HaveCount(3); - xsdUnhandledEnumAttributesKeyword.Properties.Single(p => p.Name == "frontend").Properties.Should().HaveCount(2); - xsdUnhandledEnumAttributesKeyword.Properties.Single(p => p.Name == "backend").Properties.Should().HaveCount(2); - xsdUnhandledEnumAttributesKeyword.Properties.Single(p => p.Name == "other").Properties.Should().HaveCount(2); + Assert.Equal(3, xsdUnhandledEnumAttributesKeyword.Properties.Count); + Assert.Equal(2, xsdUnhandledEnumAttributesKeyword.Properties.Single(p => p.Name == "frontend").Properties.Count); + Assert.Equal(2, xsdUnhandledEnumAttributesKeyword.Properties.Single(p => p.Name == "backend").Properties.Count); + Assert.Equal(2, xsdUnhandledEnumAttributesKeyword.Properties.Single(p => p.Name == "other").Properties.Count); } [Fact] @@ -62,7 +61,7 @@ public void Write_ValidStructure_ShouldWriteToJson() var streamReader = new StreamReader(jsonStream); var jsonText = streamReader.ReadToEnd(); - jsonText.Should().Be(@"{""@xsdUnhandledEnumAttributes"":{""frontend"":{""seres:elementtype"":""Datakodeelement"",""seres:guid"":""http://seres.no/guid/Kursdomene/Datakodeelement/other/784952""},""backend"":{""seres:elementtype"":""Datakodeelement"",""seres:guid"":""http://seres.no/guid/Kursdomene/Datakodeelement/other/784951""},""other"":{""seres:elementtype"":""Datakodeelement"",""seres:guid"":""http://seres.no/guid/Kursdomene/Datakodeelement/other/784950""}}}"); + Assert.Equal(@"{""@xsdUnhandledEnumAttributes"":{""frontend"":{""seres:elementtype"":""Datakodeelement"",""seres:guid"":""http://seres.no/guid/Kursdomene/Datakodeelement/other/784952""},""backend"":{""seres:elementtype"":""Datakodeelement"",""seres:guid"":""http://seres.no/guid/Kursdomene/Datakodeelement/other/784951""},""other"":{""seres:elementtype"":""Datakodeelement"",""seres:guid"":""http://seres.no/guid/Kursdomene/Datakodeelement/other/784950""}}}", jsonText); } } } diff --git a/backend/tests/DataModeling.Tests/Json/SeresStrategyTests.cs b/backend/tests/DataModeling.Tests/Json/SeresStrategyTests.cs index 69853099417..1a4bde0d184 100644 --- a/backend/tests/DataModeling.Tests/Json/SeresStrategyTests.cs +++ b/backend/tests/DataModeling.Tests/Json/SeresStrategyTests.cs @@ -1,8 +1,8 @@ +using System.Linq; using System.Threading.Tasks; using Altinn.Studio.DataModeling.Converter.Json; using Altinn.Studio.DataModeling.Converter.Json.Strategy; using Altinn.Studio.DataModeling.Json.Keywords; -using FluentAssertions; using Json.Pointer; using SharedResources.Tests; using Xunit; @@ -22,10 +22,11 @@ public Task Analyze_Seres_Converted_JsonSchema(string path) var metadata = analyzer.AnalyzeSchema(schema); - metadata.GetCompatibleTypes(JsonPointer.Parse("#")).Should().Equal(CompatibleXsdType.ComplexType); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/oneOf/[0]")).Should().Equal(CompatibleXsdType.ComplexType); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/melding-modell")).Should().Equal(CompatibleXsdType.ComplexType); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/melding-modell/properties/e1")).Should().Equal(CompatibleXsdType.SimpleType); + Assert.Equal(CompatibleXsdType.ComplexType, metadata.GetCompatibleTypes(JsonPointer.Parse("#")).Single()); + Assert.Equal(CompatibleXsdType.ComplexType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/oneOf/[0]")).Single()); + Assert.Equal(CompatibleXsdType.ComplexType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/melding-modell")).Single()); + Assert.Equal(CompatibleXsdType.SimpleType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/melding-modell/properties/e1")).Single()); + return Task.CompletedTask; } @@ -40,7 +41,8 @@ public Task Analyze_SimpleContent_Extension(string path) var metadata = analyzer.AnalyzeSchema(schema); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/myBase")).Should().Contain(new[] { CompatibleXsdType.ComplexType, CompatibleXsdType.SimpleContentExtension }); + Assert.Contains(CompatibleXsdType.ComplexType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/myBase"))); + Assert.Contains(CompatibleXsdType.SimpleContentExtension, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/myBase"))); return Task.CompletedTask; } @@ -55,36 +57,37 @@ public Task Analyze_SimpleType_Restriction(string path) var metadata = analyzer.AnalyzeSchema(schema); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/t1")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/t2")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/t3")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/t4")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/n1")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/n2")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f1")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f2")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f3")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f4")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f5")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f6")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/c0")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/simpleString")).Should().Contain(CompatibleXsdType.SimpleType); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/SeresType")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/simpleString")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/stringMinMaxLengthRestrictions")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/stringLengthRestrictions")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/stringEnumRestrictions")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/stringPatternRestrictions")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictions")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictions2")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional0")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional1")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional2")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional3")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional4")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional5")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/complexStructure")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, + metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/t1"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/t2"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/t3"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/t4"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/n1"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/n2"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f1"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f2"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f3"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f4"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f5"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/f6"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/properties/c0"))); + + Assert.Contains(CompatibleXsdType.SimpleType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/simpleString"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/SeresType"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/simpleString"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/stringMinMaxLengthRestrictions"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/stringLengthRestrictions"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/stringEnumRestrictions"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/stringPatternRestrictions"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictions"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictions2"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional0"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional1"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional2"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional3"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional4"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/numberRestrictionsFractional5"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/complexStructure"))); return Task.CompletedTask; } @@ -99,13 +102,13 @@ public Task Analyze_SimpleContent_Restriction(string path) var metadata = analyzer.AnalyzeSchema(schema); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/ageType")).Should().Contain(CompatibleXsdType.SimpleType); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/limitedAgeType")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/genderType")).Should().Contain(CompatibleXsdType.SimpleType); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/limitedGenderType")).Should().Contain(CompatibleXsdType.SimpleTypeRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/person")).Should().Contain(CompatibleXsdType.SimpleContentExtension); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/limitedPerson")).Should().Contain(CompatibleXsdType.SimpleContentRestriction); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/limitedPerson-inline")).Should().Contain(CompatibleXsdType.SimpleContentRestriction); + Assert.Contains(CompatibleXsdType.SimpleType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/ageType"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/limitedAgeType"))); + Assert.Contains(CompatibleXsdType.SimpleType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/genderType"))); + Assert.Contains(CompatibleXsdType.SimpleTypeRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/limitedGenderType"))); + Assert.Contains(CompatibleXsdType.SimpleContentExtension, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/person"))); + Assert.Contains(CompatibleXsdType.SimpleContentRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/limitedPerson"))); + Assert.Contains(CompatibleXsdType.SimpleContentRestriction, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/limitedPerson-inline"))); return Task.CompletedTask; } @@ -120,10 +123,11 @@ public Task Analyze_ComplexContent_Extension(string path) var metadata = analyzer.AnalyzeSchema(schema); - metadata.GetCompatibleTypes(JsonPointer.Parse("#")).Should().Contain(CompatibleXsdType.ComplexContentExtension); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/myBase")).Should().Contain(CompatibleXsdType.ComplexType); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/badBoy")).Should().Contain(CompatibleXsdType.ComplexType); - metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/badBoy/properties/reallyNasty")).Should().Contain(CompatibleXsdType.ComplexContentExtension); + Assert.Contains(CompatibleXsdType.ComplexContentExtension, metadata.GetCompatibleTypes(JsonPointer.Parse("#"))); + Assert.Contains(CompatibleXsdType.ComplexType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/myBase"))); + Assert.Contains(CompatibleXsdType.ComplexType, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/badBoy"))); + Assert.Contains(CompatibleXsdType.ComplexContentExtension, metadata.GetCompatibleTypes(JsonPointer.Parse("#/$defs/badBoy/properties/reallyNasty"))); + return Task.CompletedTask; } } diff --git a/backend/tests/DataModeling.Tests/JsonMetadataParserTests.cs b/backend/tests/DataModeling.Tests/JsonMetadataParserTests.cs index cb9c80eccad..3a432f7487c 100644 --- a/backend/tests/DataModeling.Tests/JsonMetadataParserTests.cs +++ b/backend/tests/DataModeling.Tests/JsonMetadataParserTests.cs @@ -1,5 +1,4 @@ using DataModeling.Tests.BaseClasses; -using FluentAssertions; using Xunit; namespace DataModeling.Tests @@ -11,9 +10,10 @@ public void CreateModelFromMetadata_InputModelWithRestrictionMinimumAndMaximum_G { Given.That.ModelMetadataLoaded( "Model/Metadata/restriction-total-digits.json") - .When.ModelMetadataConvertedToCsharpClass() - .Then.CSharpClasses.Should().NotBeNull(); - And.CSharpClasses.Should().Contain("[Range(-7.766279631452242E+18, 7.766279631452242E+18)]"); + .When.ModelMetadataConvertedToCsharpClass(); + + Assert.NotNull(CSharpClasses); + Assert.Contains("[Range(-7.766279631452242E+18, 7.766279631452242E+18)]", CSharpClasses); } [Fact] @@ -21,10 +21,12 @@ public void CreateModelFromMetadata_InputModelWithRestrictionMinLengthAndMaxLeng { Given.That.ModelMetadataLoaded( "Model/Metadata/restriction-total-digits.json") - .When.ModelMetadataConvertedToCsharpClass() - .Then.CSharpClasses.Should().NotBeNull(); - And.CSharpClasses.Should().Contain("[MinLength(1)]"); - And.CSharpClasses.Should().Contain("[MaxLength(20)]"); + .When.ModelMetadataConvertedToCsharpClass(); + + Assert.NotNull(CSharpClasses); + + Assert.Contains("[MinLength(1)]", CSharpClasses); + Assert.Contains("[MaxLength(20)]", CSharpClasses); } [Fact] @@ -32,9 +34,11 @@ public void CreateModelFromMetadata_InputModelSpecifiedModelName_GenerateDataAnn { Given.That.ModelMetadataLoaded( "Model/Metadata/RA-0678_M.json") - .When.ModelMetadataConvertedToCsharpClass() - .Then.CSharpClasses.Should().NotBeNull(); - And.CSharpClasses.Should().Contain("[XmlRoot(ElementName=\"melding\")]"); + .When.ModelMetadataConvertedToCsharpClass(); + + Assert.NotNull(CSharpClasses); + + Assert.Contains("[XmlRoot(ElementName=\"melding\")]", CSharpClasses); } [Fact] @@ -42,11 +46,13 @@ public void CreateModelFromMetadata_StringArrayShouldUseNativeType() { Given.That.ModelMetadataLoaded( "Model/Metadata/SimpleStringArray.json") - .When.ModelMetadataConvertedToCsharpClass() - .Then.CSharpClasses.Should().NotBeNull(); - And.CSharpClasses.Should().Contain("List<string>"); - And.CSharpClasses.Should().NotContain("List<String>"); - And.CSharpClasses.Should().NotContain("public class String"); + .When.ModelMetadataConvertedToCsharpClass(); + + Assert.NotNull(CSharpClasses); + + Assert.Contains("List<string>", CSharpClasses); + Assert.DoesNotContain("List<String>", CSharpClasses); + Assert.DoesNotContain("public class String", CSharpClasses); } [Fact] @@ -54,9 +60,12 @@ public void CreateModelFromMetadata_TargetNamespaceShouldBeCarriedOverToClass() { Given.That.ModelMetadataLoaded( "Model/Metadata/SeresBasicSchemaWithTargetNamespace.json") - .When.ModelMetadataConvertedToCsharpClass() - .Then.CSharpClasses.Should().NotBeNull(); - And.CSharpClasses.Should().MatchRegex("\\[XmlRoot\\(.*Namespace=\"urn:no:altinn:message\"\\)\\]"); + .When.ModelMetadataConvertedToCsharpClass(); + + Assert.NotNull(CSharpClasses); + + Assert.Matches("\\[XmlRoot\\(.*Namespace=\"urn:no:altinn:message\"\\)\\]", CSharpClasses); + } } } diff --git a/backend/tests/DataModeling.Tests/JsonSchemaToMetamodelConverterTests.cs b/backend/tests/DataModeling.Tests/JsonSchemaToMetamodelConverterTests.cs index ef321c4ea96..61f360e6827 100644 --- a/backend/tests/DataModeling.Tests/JsonSchemaToMetamodelConverterTests.cs +++ b/backend/tests/DataModeling.Tests/JsonSchemaToMetamodelConverterTests.cs @@ -4,7 +4,6 @@ using Altinn.Studio.DataModeling.Metamodel; using DataModeling.Tests.BaseClasses; using Designer.Tests.Assertions; -using FluentAssertions; using SharedResources.Tests; using Xunit; @@ -32,8 +31,9 @@ public void Convert_FromSeresSchema_ShouldConvert(string xsdSchemaPath, string e .And.ExpectedMetamodelLoaded(expectedMetamodelPath) .Then.MetamodelShouldBeEquivalentToExpected() .And.When.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then.CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); } // Helper methods @@ -53,7 +53,7 @@ private JsonSchemaToMetamodelConverterTests MetamodelShouldBeEquivalentToExpecte private JsonSchemaToMetamodelConverterTests MetamodelShouldHaveOneRootElement() { - ModelMetadata.Elements.Values.Where(e => e.ParentElement == null).ToList().Count.Should().Be(1); + Assert.Single(ModelMetadata.Elements.Values.Where(e => e.ParentElement == null).ToList()); return this; } } diff --git a/backend/tests/DataModeling.Tests/ModelSerializationDeserializationTests.cs b/backend/tests/DataModeling.Tests/ModelSerializationDeserializationTests.cs index adfde31cd54..cc2c77b331a 100644 --- a/backend/tests/DataModeling.Tests/ModelSerializationDeserializationTests.cs +++ b/backend/tests/DataModeling.Tests/ModelSerializationDeserializationTests.cs @@ -1,7 +1,6 @@ using System.Text.Json; using System.Threading.Tasks; using Altinn.Studio.DataModeling.Json.Keywords; -using FluentAssertions; using Json.Schema; using SharedResources.Tests; using Xunit; @@ -30,7 +29,7 @@ public void XmlModel_SeresBasic_ShouldValidate() var validXml = ValidateXml(xml); - validXml.Should().BeTrue(); + Assert.True(validXml); } [Fact] @@ -38,7 +37,7 @@ public void XmlModel_SeresBasic_ShouldDeserializeAndValidate() { _TestData.Model.CSharp.melding melding = DeserializeFromXmlResource(SERESBASIC_XML_RESOURCE); - melding.E1.Should().Be("Yo"); + Assert.Equal("Yo", melding.E1); } [Fact] @@ -50,7 +49,7 @@ public void CSharpModel_SeresBasic_ShouldSerializeToValidXml() bool validXml = ValidateXml(xml); - validXml.Should().BeTrue(); + Assert.True(validXml); } [Fact] @@ -62,7 +61,7 @@ public Task JsonModel_SeresBasic_ShouldValidate() var validationResults = jsonSchema.Evaluate(jsonDocument.RootElement, new EvaluationOptions() { OutputFormat = OutputFormat.Hierarchical }); - validationResults.IsValid.Should().BeTrue(); + Assert.True(validationResults.IsValid); return Task.CompletedTask; } @@ -72,7 +71,7 @@ public void JsonModel_SeresBasic_ShouldDeserializeAndValidate() var json = SharedResourcesHelper.LoadTestDataAsString(SERESBASIC_JSON_RESOURCE); _TestData.Model.CSharp.melding melding = JsonSerializer.Deserialize<_TestData.Model.CSharp.melding>(json); - melding.E1.Should().Be("Yo"); + Assert.Equal("Yo", melding.E1); } [Fact] @@ -87,7 +86,7 @@ public void CSharpModel_SeresBasic_ShouldSerializeToValidJson() var validationResults = jsonSchema.Evaluate(jsonDocument.RootElement, new EvaluationOptions() { OutputFormat = OutputFormat.Hierarchical }); - validationResults.IsValid.Should().BeTrue(); + Assert.True(validationResults.IsValid); } private static _TestData.Model.CSharp.melding DeserializeFromXmlResource(string xmlResource) diff --git a/backend/tests/DataModeling.Tests/ModelSerializationTests.cs b/backend/tests/DataModeling.Tests/ModelSerializationTests.cs index 254c281b067..ef5f0e958fa 100644 --- a/backend/tests/DataModeling.Tests/ModelSerializationTests.cs +++ b/backend/tests/DataModeling.Tests/ModelSerializationTests.cs @@ -3,7 +3,6 @@ using DataModeling.Tests.BaseClasses; using DataModeling.Tests.TestDataClasses; using DataModeling.Tests.Utils; -using FluentAssertions; using SharedResources.Tests; using Xunit; using JsonSerializer = System.Text.Json.JsonSerializer; @@ -31,9 +30,9 @@ public void Round_DeserializeAndSerialize_To_ShouldNotChangeJsonData(string xsdS .When.LoadedXsdSchemaConvertedToJsonSchema() .And.ConvertedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then - .CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); And.When.TypeReadFromCompiledAssembly(typeName) .And.JsonDataLoaded(jsonPath) @@ -50,9 +49,9 @@ public void Round_DeserializeAndSerialize_To_ShouldNotChangeXmlData(string xsdSc .When.LoadedXsdSchemaConvertedToJsonSchema() .And.ConvertedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then - .CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); And.When.TypeReadFromCompiledAssembly(typeName) .And.XmlDataLoaded(jsonPath) @@ -69,9 +68,9 @@ public void XmlAndJsonData_ShouldDeserialize_ToEquivalentModel(string xsdSchemaP .When.LoadedXsdSchemaConvertedToJsonSchema() .And.ConvertedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then - .CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); And.When.TypeReadFromCompiledAssembly(typeName) .And.XmlDataLoaded(xmlPath) @@ -109,7 +108,7 @@ private ModelSerializationTests ModelObjectSerializedToJson() private void SerializedJsonData_ShouldNotBeChanged() { - JsonUtils.DeepEquals(SerializedModelJson, JsonData).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(SerializedModelJson, JsonData)); } // Xml helper methods @@ -143,7 +142,7 @@ private void SerializedXmlData_ShouldNotBeChanged() private void ModelObjects_ShouldBeEquivalent() { - DeserializedJsonModelObject.Should().BeEquivalentTo(DeserializedXmlModelObject); + Assert.True(JsonUtils.DeepEquals(JsonSerializer.Serialize(DeserializedJsonModelObject), JsonSerializer.Serialize(DeserializedXmlModelObject))); } } } diff --git a/backend/tests/DataModeling.Tests/Templates/GeneralJsonTemplateTests.cs b/backend/tests/DataModeling.Tests/Templates/GeneralJsonTemplateTests.cs index ab8aee98a4f..0193c02a74f 100644 --- a/backend/tests/DataModeling.Tests/Templates/GeneralJsonTemplateTests.cs +++ b/backend/tests/DataModeling.Tests/Templates/GeneralJsonTemplateTests.cs @@ -2,7 +2,6 @@ using Altinn.Studio.DataModeling.Json.Keywords; using Altinn.Studio.DataModeling.Templates; using Altinn.Studio.DataModeling.Utils; -using FluentAssertions; using Json.Schema; using Xunit; @@ -25,11 +24,13 @@ public void Constructor_TemplateExists_ShouldSetCorrectValues() // Assert JsonSchema jsonSchema = JsonSchema.FromText(actualJsonTemplate.GetJsonString()); var idKeyword = jsonSchema.GetKeywordOrNull<IdKeyword>(); - idKeyword.Id.Should().Be(expectedId); - jsonSchema.GetKeywordOrNull<XsdRootElementKeyword>().Value.Should().Be(expectedModelName); + Assert.Equal(expectedId, idKeyword.Id.ToString()); + + Assert.Equal(expectedModelName, jsonSchema.GetKeywordOrNull<XsdRootElementKeyword>().Value); var properties = jsonSchema.GetKeywordOrNull<PropertiesKeyword>(); - properties.Properties.Should().NotBeEmpty(); - properties.Properties.Count.Should().Be(3); + + Assert.NotEmpty(properties.Properties); + Assert.Equal(3, properties.Properties.Count); } } } diff --git a/backend/tests/DataModeling.Tests/Templates/SeresJsonTemplateTests.cs b/backend/tests/DataModeling.Tests/Templates/SeresJsonTemplateTests.cs index 5772375453a..165f1db1861 100644 --- a/backend/tests/DataModeling.Tests/Templates/SeresJsonTemplateTests.cs +++ b/backend/tests/DataModeling.Tests/Templates/SeresJsonTemplateTests.cs @@ -5,7 +5,6 @@ using Altinn.Studio.DataModeling.Json.Keywords; using Altinn.Studio.DataModeling.Templates; using Altinn.Studio.DataModeling.Utils; -using FluentAssertions; using Json.Pointer; using Json.Schema; using Xunit; @@ -28,14 +27,15 @@ public void Constructor_TemplateExists_ShouldSetCorrectValues() // Assert JsonSchema jsonSchema = JsonSchema.FromText(actualJsonTemplate.GetJsonString()); var idKeyword = jsonSchema.GetKeywordOrNull<IdKeyword>(); - idKeyword.Id.Should().Be(expectedId); + Assert.Equal(expectedId, idKeyword.Id.ToString()); var infoKeyword = jsonSchema.GetKeywordOrNull<InfoKeyword>(); var value = infoKeyword.Value; - value.GetProperty("meldingsnavn").GetString().Should().Be("melding"); - value.GetProperty("modellnavn").GetString().Should().Be("melding-modell"); - var messageType = jsonSchema.FollowReference(JsonPointer.Parse("#/$defs/melding-modell")).Should().NotBeNull(); + Assert.Equal("melding", value.GetProperty("meldingsnavn").GetString()); + Assert.Equal("melding-modell", value.GetProperty("modellnavn").GetString()); + + Assert.NotNull(jsonSchema.FollowReference(JsonPointer.Parse("#/$defs/melding-modell"))); } [Fact] @@ -49,17 +49,17 @@ public void TemplateShouldBeValidSeresXsd() XmlSchema xsd = ConvertJsonSchema(jsonSchema); - xsd.Items.Count.Should().Be(3); - xsd.Items[2].GetName().Should().Be("melding-modell"); + Assert.Equal(3, xsd.Items.Count); + Assert.Equal("melding-modell", xsd.Items[2].GetName()); - XmlSchemaComplexType complexType = xsd.Items[2].As<XmlSchemaComplexType>(); - var attributes = complexType.Attributes.Count.Should().Be(3); + XmlSchemaComplexType complexType = xsd.Items[2] as XmlSchemaComplexType; + Assert.Equal(3, complexType.Attributes.Count); foreach (XmlSchemaAttribute attribute in complexType.Attributes) { if (attribute.Name == "dataFormatProvider") { - attribute.FixedValue.Should().Be("SERES"); + Assert.Equal("SERES", attribute.FixedValue); } } } diff --git a/backend/tests/DataModeling.Tests/Utils/JsonSchemaNavigationExtensionsTests.cs b/backend/tests/DataModeling.Tests/Utils/JsonSchemaNavigationExtensionsTests.cs index 61cdd6860a1..f936139322a 100644 --- a/backend/tests/DataModeling.Tests/Utils/JsonSchemaNavigationExtensionsTests.cs +++ b/backend/tests/DataModeling.Tests/Utils/JsonSchemaNavigationExtensionsTests.cs @@ -1,5 +1,4 @@ using Altinn.Studio.DataModeling.Utils; -using FluentAssertions; using Json.Pointer; using Json.Schema; using Xunit; @@ -21,6 +20,7 @@ public void Navigation_Should_Resolve_DeeplyNestedReferences() ("test", new JsonSchemaBuilder().Type(SchemaValueType.String)))))); var result = schema.FollowReference(JsonPointer.Parse(@"#/$defs/test/items/properties/test")); - result.Should().NotBeNull(); + + Assert.NotNull(result); } } diff --git a/backend/tests/DataModeling.Tests/Utils/XmlSchemaTypesTests.cs b/backend/tests/DataModeling.Tests/Utils/XmlSchemaTypesTests.cs index 9ac3b29bcd5..33d06bc833f 100644 --- a/backend/tests/DataModeling.Tests/Utils/XmlSchemaTypesTests.cs +++ b/backend/tests/DataModeling.Tests/Utils/XmlSchemaTypesTests.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using Altinn.Studio.DataModeling.Utils; -using FluentAssertions; using Xunit; namespace DataModeling.Tests.Utils; @@ -12,13 +11,13 @@ public class XmlSchemaTypesTests [MemberData(nameof(TestData))] public void AllTypesShouldContainType(string type) { - XmlSchemaTypes.AllKnownTypes.Should().Contain(type); + Assert.Contains(type, XmlSchemaTypes.AllKnownTypes); } [Fact] public void AllTypesShouldHave50Types() { - XmlSchemaTypes.AllKnownTypes.Count().Should().Be(49); + Assert.Equal(49, XmlSchemaTypes.AllKnownTypes.Count()); } public static IEnumerable<object[]> TestData => new List<object[]> diff --git a/backend/tests/DataModeling.Tests/XmlDeserializeSerializeTests.cs b/backend/tests/DataModeling.Tests/XmlDeserializeSerializeTests.cs index d5f011bc8bb..6aba8995c6d 100644 --- a/backend/tests/DataModeling.Tests/XmlDeserializeSerializeTests.cs +++ b/backend/tests/DataModeling.Tests/XmlDeserializeSerializeTests.cs @@ -4,7 +4,6 @@ using System.Xml.Serialization; using DataModeling.Tests.BaseClasses; using DataModeling.Tests.Utils; -using FluentAssertions; using SharedResources.Tests; using Xunit; @@ -20,15 +19,16 @@ public void XmlDeserializeSerializeShouldKeepNillValue(string xsdSchemaPath, str .When.LoadedXsdSchemaConvertedToJsonSchema() .And.ConvertedJsonSchemaConvertedToModelMetadata() .And.ModelMetadataConvertedToCsharpClass() - .And.CSharpClassesCompiledToAssembly() - .Then.CompiledAssembly.Should().NotBeNull(); + .And.CSharpClassesCompiledToAssembly(); + + Assert.NotNull(CompiledAssembly); And.DeserializeAndSerializeShouldProduceSameXml(xmlPath); } private void DeserializeAndSerializeShouldProduceSameXml(string xmlPath) { - Type csharpType = CompiledAssembly.Types().Single(type => type.CustomAttributes.Any(att => att.AttributeType == typeof(XmlRootAttribute))); + Type csharpType = CompiledAssembly.GetTypes().Single(type => type.CustomAttributes.Any(att => att.AttributeType == typeof(XmlRootAttribute))); string loadedXml = SharedResourcesHelper.LoadTestDataAsString(xmlPath); diff --git a/backend/tests/Designer.Tests/Controllers/AnsattPortenController/AuthStatusTests.cs b/backend/tests/Designer.Tests/Controllers/AnsattPortenController/AuthStatusTests.cs index cb44ab9b2b6..3f6a1a739ae 100644 --- a/backend/tests/Designer.Tests/Controllers/AnsattPortenController/AuthStatusTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AnsattPortenController/AuthStatusTests.cs @@ -5,7 +5,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.AnsattPortenController.Base; using Designer.Tests.Controllers.ApiTests; -using FluentAssertions; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -54,10 +53,10 @@ public async Task AuthStatus_Should_ReturnFalse_IfNotAuthenticated() using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, VersionPrefix); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); AuthStatus authStatus = await response.Content.ReadAsAsync<AuthStatus>(); - authStatus.IsLoggedIn.Should().BeFalse(); + Assert.False(authStatus.IsLoggedIn); } [Fact] @@ -75,9 +74,9 @@ public async Task AuthStatus_Should_ReturnTrue_IfAuthenticated() using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, VersionPrefix); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); AuthStatus authStatus = await response.Content.ReadAsAsync<AuthStatus>(); - authStatus.IsLoggedIn.Should().BeTrue(); + Assert.True(authStatus.IsLoggedIn); } } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/AddLayoutSetTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/AddLayoutSetTests.cs index 30ae9aa141d..3c05513bb99 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/AddLayoutSetTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/AddLayoutSetTests.cs @@ -12,8 +12,8 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; +using SharedResources.Tests; using Xunit; namespace Designer.Tests.Controllers.AppDevelopmentController @@ -45,14 +45,14 @@ public async Task AddLayoutSets_NewSet_ReturnsOk(string org, string app, string }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); LayoutSets layoutSetsAfter = await GetLayoutSetsFile(org, targetRepository, developer); - layoutSetsBefore.Schema.Should().NotBeNull(); + Assert.NotNull(layoutSetsBefore.Schema); Assert.False(layoutSetsBefore.Sets.Exists(set => set.Id == newLayoutSetConfig.Id)); - layoutSetsBefore.Sets.Count.Should().Be(layoutSetsAfter.Sets.Count - 1); - layoutSetsAfter.Schema.Should().NotBeNull(); + Assert.Equal(layoutSetsAfter.Sets.Count - 1, layoutSetsBefore.Sets.Count); + Assert.NotNull(layoutSetsAfter.Schema); Assert.True(layoutSetsAfter.Sets.Exists(set => set.Id == newLayoutSetConfig.Id)); } @@ -75,7 +75,7 @@ public async Task AddLayoutSet_NewLayoutSetIdExistsBefore_ReturnsOKButWithConfli }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); Dictionary<string, string> responseMessage = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent); Assert.Equal($"Layout set name, {layoutSetId}, already exists.", responseMessage["infoMessage"]); @@ -101,7 +101,7 @@ public async Task AddLayoutSet_NewLayoutSetTaskIdExistsBefore_ReturnsOKButWithCo }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); Dictionary<string, string> responseMessage = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent); Assert.Equal($"Layout set with task, {existingTaskId}, already exists.", responseMessage["infoMessage"]); @@ -126,7 +126,7 @@ public async Task AddLayoutSet_NewLayoutSetIdIsEmpty_ReturnsBadRequest(string or }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Theory] @@ -148,7 +148,7 @@ public async Task AddLayoutSet_TaskTypeIsNull_AddsLayoutSetAndReturnsOk(string o }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Theory] @@ -170,7 +170,7 @@ public async Task AddLayoutSet_TaskTypeIsPayment_AddsLayoutSetWithPaymentCompone }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); JsonNode initialLayout = await GetLayoutFile(org, targetRepository, developer, layoutSetId); @@ -182,8 +182,9 @@ public async Task AddLayoutSet_TaskTypeIsPayment_AddsLayoutSetWithPaymentCompone }; JsonArray layout = initialLayout["data"]["layout"] as JsonArray; - layout.Count.Should().Be(1); - initialLayout["data"]["layout"][0].Should().BeEquivalentTo(defaultComponent, options => options.RespectingRuntimeTypes().IgnoringCyclicReferences()); + + Assert.Single(layout); + Assert.True(JsonUtils.DeepEquals(defaultComponent.ToJsonString(), initialLayout["data"]["layout"][0].ToJsonString())); } [Theory] @@ -205,7 +206,7 @@ public async Task AddLayoutSet_AppWithoutLayoutSets_ReturnsNotFound(string org, }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } private async Task<LayoutSets> GetLayoutSetsFile(string org, string app, string developer) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/DeleteFormLayoutTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/DeleteFormLayoutTests.cs index 07ad105e57f..7cbae75c438 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/DeleteFormLayoutTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/DeleteFormLayoutTests.cs @@ -1,12 +1,13 @@ using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc.Testing; +using SharedResources.Tests; using Xunit; namespace Designer.Tests.Controllers.AppDevelopmentController @@ -31,13 +32,13 @@ public async Task DeleteFormLayout_ShouldDeleteLayoutFile_AndReturnOk(string org using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string relativePath = string.IsNullOrEmpty(layoutSetName) ? $"App/ui/layouts/{layoutName}.json" : $"App/ui/{layoutSetName}/layouts/{layoutName}.json"; string layoutFilePath = Path.Combine(TestRepoPath, relativePath); - File.Exists(layoutFilePath).Should().BeFalse(); + Assert.False(File.Exists(layoutFilePath)); } [Theory] @@ -53,7 +54,38 @@ public async Task DeleteFormLayout_NonExistingFile_Should_AndReturnNotFound(stri using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("ttd", "testUser", "layout", "Side2")] + public async Task DeleteFormLayout_DeletesAssociatedSummary2Components_ReturnsOk(string org, string developer, string layoutSetName, string layoutName) + { + string actualApp = "app-with-summary2-components"; + string app = TestDataHelper.GenerateTestRepoName(); + await CopyRepositoryForTest(org, actualApp, developer, app); + + string url = $"{VersionPrefix(org, app)}/form-layout/{layoutName}?layoutSetName={layoutSetName}"; + using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); + + using var response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + string expectedApp = "app-with-summary2-components-after-deleting-references"; + + string[] layoutPaths = [ + "layout/layouts/Side1.json", + "layout/layouts/Side2.json", + "layout2/layouts/Side1.json", + "layout2/layouts/Side2.json", + ]; + + layoutPaths.ToList().ForEach(file => + { + string actual = TestDataHelper.GetFileFromRepo(org, app, developer, $"App/ui/{file}"); + string expected = TestDataHelper.GetFileFromRepo(org, expectedApp, developer, $"App/ui/{file}"); + Assert.True(JsonUtils.DeepEquals(actual, expected)); + }); } } } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/DeleteLayoutSetTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/DeleteLayoutSetTests.cs index 8acf823a8a2..9e233eb9a03 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/DeleteLayoutSetTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/DeleteLayoutSetTests.cs @@ -10,7 +10,6 @@ using Altinn.Studio.Designer.Models; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -37,12 +36,12 @@ public async Task DeleteLayoutSet_SetWithoutDataTypeConnection_ReturnsOk(string using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); LayoutSets layoutSetsAfter = await GetLayoutSetsFile(org, targetRepository, developer); Assert.True(layoutSetsBefore.Sets.Exists(set => set.Id == layoutSetToDeleteId)); - layoutSetsAfter.Sets.Should().HaveCount(layoutSetsBefore.Sets.Count - 1); + Assert.Equal(layoutSetsBefore.Sets.Count - 1, layoutSetsAfter.Sets.Count); Assert.False(layoutSetsAfter.Sets.Exists(set => set.Id == layoutSetToDeleteId)); } @@ -63,16 +62,15 @@ public async Task DeleteLayoutSet_SetWithDataTypeConnection_ReturnsOk(string org using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); LayoutSets layoutSetsAfter = await GetLayoutSetsFile(org, targetRepository, developer); Application appMetadataAfter = await GetApplicationMetadataFile(org, targetRepository, developer); - appMetadataBefore.DataTypes.Find(dataType => dataType.Id == connectedDataType).TaskId.Should() - .Be(connectedTaskId); + Assert.Equal(connectedTaskId, appMetadataBefore.DataTypes.Find(dataType => dataType.Id == connectedDataType).TaskId); Assert.True(layoutSetsBefore.Sets.Exists(set => set.Id == layoutSetToDeleteId)); - layoutSetsAfter.Sets.Should().HaveCount(layoutSetsBefore.Sets.Count - 1); - appMetadataAfter.DataTypes.Find(dataType => dataType.Id == connectedDataType).TaskId.Should().BeNull(); + Assert.Equal(layoutSetsBefore.Sets.Count - 1, layoutSetsAfter.Sets.Count); + Assert.Null(appMetadataAfter.DataTypes.Find(dataType => dataType.Id == connectedDataType).TaskId); Assert.False(layoutSetsAfter.Sets.Exists(set => set.Id == layoutSetToDeleteId)); } @@ -91,7 +89,7 @@ public async Task DeleteLayoutSet_DeletesRelatedLayoutSetFolder_ReturnsOk(string using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.False(LayoutSetFolderExists(org, targetRepository, developer, layoutSetToDeleteId)); } @@ -111,7 +109,7 @@ public async Task DeleteLayoutSet_IdNotFound_ReturnsUnAlteredLayoutSets(string o using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); @@ -131,7 +129,7 @@ public async Task DeleteLayoutSet_AppWithoutLayoutSets_ReturnsNotFound(string or using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Theory] @@ -147,17 +145,51 @@ public async Task DeleteLayoutSet_RemovesComponentsReferencingLayoutSet(string o using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK, await response.Content.ReadAsStringAsync()); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); JsonNode formLayout = (await GetFormLayouts(org, targetRepository, developer, layoutSetWithRef))[layoutSetFile]; JsonArray layout = formLayout["data"]?["layout"] as JsonArray; - layout.Should().NotBeNull(); - layout + bool componentsReferencingDeletedLayoutSet = layout .Where(jsonNode => jsonNode["layoutSet"] != null) - .Should() - .NotContain(jsonNode => jsonNode["layoutSet"].GetValue<string>() == deletedComponentId, - $"No components should reference the deleted layout set {deletedComponentId}"); + .Any(jsonNode => jsonNode["layoutSet"].GetValue<string>() == deletedComponentId); + + Assert.False(componentsReferencingDeletedLayoutSet, $"No components should reference the deleted layout set {deletedComponentId}"); + + Assert.NotNull(layout); + + + } + + [Theory] + [InlineData("ttd", "testUser", "layoutSet")] + public async Task DeleteLayoutSet_DeletesAssociatedSummary2Components_ReturnsOk(string org, string developer, string layoutSetName) + { + string actualApp = "app-with-summary2-components"; + string app = TestDataHelper.GenerateTestRepoName(); + await CopyRepositoryForTest(org, actualApp, developer, app); + + string url = $"{VersionPrefix(org, app)}/layout-set/{layoutSetName}"; + using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, url); + + using var response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + string expectedApp = "app-with-summary2-components-after-deleting-references"; + + string[] layoutPaths = [ + "layoutSet/layouts/Side1.json", + "layoutSet/layouts/Side2.json", + "layoutSet2/layouts/Side1.json", + "layoutSet2/layouts/Side2.json" + ]; + + layoutPaths.ToList().ForEach(file => + { + string actual = TestDataHelper.GetFileFromRepo(org, app, developer, $"App/ui/{file}"); + string expected = TestDataHelper.GetFileFromRepo(org, expectedApp, developer, $"App/ui/{file}"); + Assert.True(JsonUtils.DeepEquals(actual, expected)); + }); } private async Task<LayoutSets> GetLayoutSetsFile(string org, string app, string developer) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/FileSync/ComponentIdChangeTests/LayoutFilesSyncComponentIdsTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/FileSync/ComponentIdChangeTests/LayoutFilesSyncComponentIdsTests.cs index d70c24df249..3e742b18911 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/FileSync/ComponentIdChangeTests/LayoutFilesSyncComponentIdsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/FileSync/ComponentIdChangeTests/LayoutFilesSyncComponentIdsTests.cs @@ -10,7 +10,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -52,7 +51,7 @@ public async Task SaveFormLayoutWithComponentIdChanges_ShouldSyncRepeatingGroupO Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutName}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); @@ -67,8 +66,8 @@ public async Task SaveFormLayoutWithComponentIdChanges_ShouldSyncRepeatingGroupO ?.SelectMany(rowsAfter => rowsAfter["cells"]?.AsArray() ?? new JsonArray()) .Any(cell => cell["component"]?.ToString() == newComponentId) ?? false; - containsOldId.Should().BeFalse(); - containsNewId.Should().BeTrue(); + Assert.False(containsOldId); + Assert.True(containsNewId); } [Theory] @@ -97,7 +96,7 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInRepeatingGroup, Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutNameThatIsAffected}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); @@ -112,8 +111,8 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInRepeatingGroup, ?.SelectMany(rowsAfter => rowsAfter["cells"]?.AsArray() ?? new JsonArray()) .Any(cell => cell["component"]?.ToString() == newComponentId) ?? false; - containsOldId.Should().BeFalse(); - containsNewId.Should().BeTrue(); + Assert.False(containsOldId); + Assert.True(containsNewId); } [Theory] @@ -137,7 +136,7 @@ public async Task SaveFormLayoutWithComponentIdChanges_ShouldSyncExpressionOccur Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutName}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); @@ -145,17 +144,17 @@ public async Task SaveFormLayoutWithComponentIdChanges_ShouldSyncExpressionOccur JsonNode componentWithExpression = layout["data"]["layout"]?.AsArray()?.FirstOrDefault(item => item["id"]?.ToString() == "header-rep2"); JsonArray expressionOnTitle = componentWithExpression?["textResourceBindings"]?["title"].AsArray(); - ContainsValue(expressionOnLayout, oldComponentId).Should().BeFalse(); - ContainsValue(expressionOnTitle, oldComponentId).Should().BeFalse(); + Assert.False(ContainsValue(expressionOnLayout, oldComponentId)); + Assert.False(ContainsValue(expressionOnTitle, oldComponentId)); if (string.IsNullOrEmpty(newComponentId)) { - ContainsValue(expressionOnLayout, newComponentId).Should().BeFalse(); - ContainsValue(expressionOnTitle, newComponentId).Should().BeFalse(); + Assert.False(ContainsValue(expressionOnLayout, newComponentId)); + Assert.False(ContainsValue(expressionOnTitle, newComponentId)); } else { - ContainsValue(expressionOnLayout, newComponentId).Should().BeTrue(); - ContainsValue(expressionOnTitle, newComponentId).Should().BeTrue(); + Assert.True(ContainsValue(expressionOnLayout, newComponentId)); + Assert.True(ContainsValue(expressionOnTitle, newComponentId)); } } @@ -185,7 +184,7 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInExpression, Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutNameThatIsAffected}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); @@ -193,17 +192,17 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInExpression, JsonNode componentWithExpression = layout["data"]["layout"]?.AsArray()?.FirstOrDefault(item => item["id"]?.ToString() == "header-rep2"); JsonArray expressionOnTitle = componentWithExpression?["textResourceBindings"]?["title"].AsArray(); - ContainsValue(expressionOnLayout, oldComponentId).Should().BeFalse(); - ContainsValue(expressionOnTitle, oldComponentId).Should().BeFalse(); + Assert.False(ContainsValue(expressionOnLayout, oldComponentId)); + Assert.False(ContainsValue(expressionOnTitle, oldComponentId)); if (string.IsNullOrEmpty(newComponentId)) { - ContainsValue(expressionOnLayout, newComponentId).Should().BeFalse(); - ContainsValue(expressionOnTitle, newComponentId).Should().BeFalse(); + Assert.False(ContainsValue(expressionOnLayout, newComponentId)); + Assert.False(ContainsValue(expressionOnTitle, newComponentId)); } else { - ContainsValue(expressionOnLayout, newComponentId).Should().BeTrue(); - ContainsValue(expressionOnTitle, newComponentId).Should().BeTrue(); + Assert.True(ContainsValue(expressionOnLayout, newComponentId)); + Assert.True(ContainsValue(expressionOnTitle, newComponentId)); } } @@ -228,7 +227,7 @@ public async Task SaveFormLayoutWithComponentIdChanges_ShouldSyncSummaryRefOccur Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutName}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); @@ -236,11 +235,11 @@ public async Task SaveFormLayoutWithComponentIdChanges_ShouldSyncSummaryRefOccur if (string.IsNullOrEmpty(newComponentId)) { - summaryComponentWithComponentRef["componentRef"].Should().BeNull(); + Assert.Null(summaryComponentWithComponentRef["componentRef"]); } else { - summaryComponentWithComponentRef["componentRef"].ToString().Should().Be(newComponentId); + Assert.Equal(newComponentId, summaryComponentWithComponentRef["componentRef"].ToString()); } } @@ -270,7 +269,7 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInSummaryComponent, Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutNameThatIsAffected}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); @@ -278,11 +277,11 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInSummaryComponent, if (string.IsNullOrEmpty(newComponentId)) { - summaryComponentWithComponentRef["componentRef"].Should().BeNull(); + Assert.Null(summaryComponentWithComponentRef["componentRef"]); } else { - summaryComponentWithComponentRef["componentRef"].ToString().Should().Be(newComponentId); + Assert.Equal(newComponentId, summaryComponentWithComponentRef["componentRef"].ToString()); } } @@ -313,20 +312,20 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceAsPropertyName, Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutName}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); JsonNode tableColumnsWithIdAsPropertyName = layout["data"]["layout"]?.AsArray()?.FirstOrDefault(item => item["id"]?.ToString() == "mainGroup2")["tableColumns"]; - tableColumnsWithIdAsPropertyName?[oldComponentId].Should().BeNull(); + Assert.Null(tableColumnsWithIdAsPropertyName?[oldComponentId]); if (string.IsNullOrEmpty(newComponentId)) { - (tableColumnsWithIdAsPropertyName as JsonObject).Count.Should().Be((originalTableColumns as JsonObject).Count - 1); + Assert.Equal((originalTableColumns as JsonObject).Count - 1, (tableColumnsWithIdAsPropertyName as JsonObject).Count); } else { - tableColumnsWithIdAsPropertyName?[newComponentId].Should().NotBeNull(); + Assert.NotNull(tableColumnsWithIdAsPropertyName?[newComponentId]); } } @@ -360,20 +359,20 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceAsPropertyName, Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutNameThatIsAffected}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); JsonNode tableColumnsWithIdAsPropertyName = layout["data"]["layout"]?.AsArray()?.FirstOrDefault(item => item["id"]?.ToString() == "mainGroup2")["tableColumns"]; - tableColumnsWithIdAsPropertyName?[oldComponentId].Should().BeNull(); + Assert.Null(tableColumnsWithIdAsPropertyName?[oldComponentId]); if (string.IsNullOrEmpty(newComponentId)) { - (tableColumnsWithIdAsPropertyName as JsonObject).Count.Should().Be((originalTableColumns as JsonObject).Count - 1); + Assert.Equal((originalTableColumns as JsonObject).Count - 1, (tableColumnsWithIdAsPropertyName as JsonObject).Count); } else { - tableColumnsWithIdAsPropertyName?[newComponentId].Should().NotBeNull(); + Assert.NotNull(tableColumnsWithIdAsPropertyName?[newComponentId]); } } @@ -398,21 +397,21 @@ public async Task SaveFormLayoutWithComponentIdChanges_ShouldSyncTableHeaderOccu Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutName}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); JsonNode repeatingGroupComponentWithIdInTableHeaders = layout["data"]["layout"]?.AsArray()?.FirstOrDefault(item => item["id"]?.ToString() == "mainGroup2"); JsonArray tableHeaders = repeatingGroupComponentWithIdInTableHeaders["tableHeaders"].AsArray(); - ContainsValue(tableHeaders, oldComponentId).Should().BeFalse(); + Assert.False(ContainsValue(tableHeaders, oldComponentId)); if (string.IsNullOrEmpty(newComponentId)) { - ContainsValue(tableHeaders, newComponentId).Should().BeFalse(); + Assert.False(ContainsValue(tableHeaders, newComponentId)); } else { - ContainsValue(tableHeaders, newComponentId).Should().BeTrue(); + Assert.True(ContainsValue(tableHeaders, newComponentId)); } } @@ -442,21 +441,21 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInTableHeaders, Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutNameThatIsAffected}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); JsonNode repeatingGroupComponentWithIdInTableHeaders = layout["data"]["layout"]?.AsArray()?.FirstOrDefault(item => item["id"]?.ToString() == "mainGroup2"); JsonArray tableHeaders = repeatingGroupComponentWithIdInTableHeaders["tableHeaders"].AsArray(); - ContainsValue(tableHeaders, oldComponentId).Should().BeFalse(); + Assert.False(ContainsValue(tableHeaders, oldComponentId)); if (string.IsNullOrEmpty(newComponentId)) { - ContainsValue(tableHeaders, newComponentId).Should().BeFalse(); + Assert.False(ContainsValue(tableHeaders, newComponentId)); } else { - ContainsValue(tableHeaders, newComponentId).Should().BeTrue(); + Assert.True(ContainsValue(tableHeaders, newComponentId)); } } @@ -487,24 +486,24 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInTableHeaders, Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetName}/layouts/{layoutNameThatIsAffected}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); JsonNode repeatingGroupComponentWithIdInTableHeaders = layout["data"]["layout"]?.AsArray()?.FirstOrDefault(item => item["id"]?.ToString() == "mainGroup2"); JsonArray tableHeaders = repeatingGroupComponentWithIdInTableHeaders["tableHeaders"].AsArray(); - ContainsValue(tableHeaders, oldComponentId).Should().BeFalse(); - ContainsValue(tableHeaders, oldComponentId2).Should().BeFalse(); + Assert.False(ContainsValue(tableHeaders, oldComponentId)); + Assert.False(ContainsValue(tableHeaders, oldComponentId2)); if (string.IsNullOrEmpty(newComponentId)) { - ContainsValue(tableHeaders, newComponentId).Should().BeFalse(); - ContainsValue(tableHeaders, newComponentId2).Should().BeFalse(); + Assert.False(ContainsValue(tableHeaders, newComponentId)); + Assert.False(ContainsValue(tableHeaders, newComponentId2)); } else { - ContainsValue(tableHeaders, newComponentId).Should().BeTrue(); - ContainsValue(tableHeaders, newComponentId2).Should().BeTrue(); + Assert.True(ContainsValue(tableHeaders, newComponentId)); + Assert.True(ContainsValue(tableHeaders, newComponentId2)); } } @@ -534,13 +533,13 @@ await AddFileToRepo(pathToLayoutWithComponentIdOccurrenceInSummaryComponent, Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutFromRepo = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/ui/{layoutSetNameForOtherLayout}/layouts/{layoutNameThatIsAffected}.json"); JsonNode layout = JsonNode.Parse(layoutFromRepo); JsonNode summaryComponentWithComponentRef = layout["data"]["layout"]?.AsArray()?.FirstOrDefault(item => item["id"]?.ToString() == "summary-1"); - summaryComponentWithComponentRef["componentRef"].ToString().Should().Be(oldComponentId); + Assert.Equal(oldComponentId, summaryComponentWithComponentRef["componentRef"].ToString()); } private string ArrangeApiRequestContent(string pathToLayoutToPost, List<ComponentIdChange> componentIdsChanges) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/FileSync/ComponentIdChangeTests/LayoutSettingsFileSyncComponentIdsTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/FileSync/ComponentIdChangeTests/LayoutSettingsFileSyncComponentIdsTests.cs index d4a7d3ffa5f..52ac136c05e 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/FileSync/ComponentIdChangeTests/LayoutSettingsFileSyncComponentIdsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/FileSync/ComponentIdChangeTests/LayoutSettingsFileSyncComponentIdsTests.cs @@ -10,7 +10,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -46,15 +45,15 @@ public async Task SaveFormLayoutWithComponentIdChanges_ShouldSyncOccurrencesInLa Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutSettingsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/ui/{layoutSetName}/Settings.json"); JsonNode layoutSettings = JsonNode.Parse(layoutSettingsFromRepo); JsonArray excludeFromPdfArray = layoutSettings["components"]?["excludeFromPdf"]?.AsArray(); - excludeFromPdfArray.Where(node => node.GetValue<string>() == oldComponentId).Should().BeEmpty(); - excludeFromPdfArray.Should().ContainSingle(node => node.GetValue<string>() == newComponentId); + Assert.DoesNotContain(excludeFromPdfArray, node => node.GetValue<string>() == oldComponentId); + Assert.Single(excludeFromPdfArray, node => node.GetValue<string>() == newComponentId); } [Theory] @@ -82,15 +81,16 @@ public async Task SaveFormLayoutWithComponentIdRemoval_ShouldRemoveOccurrencesIn Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutSettingsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/ui/{layoutSetName}/Settings.json"); JsonNode layoutSettings = JsonNode.Parse(layoutSettingsFromRepo); JsonArray excludeFromPdfArray = layoutSettings["components"]?["excludeFromPdf"]?.AsArray(); - excludeFromPdfArray.Count.Should().Be(originalExcludeFromPdfArray.Count - 1); - excludeFromPdfArray.Where(node => node.GetValue<string>() == oldComponentId).Should().BeEmpty(); + Assert.DoesNotContain(excludeFromPdfArray!, node => node.GetValue<string>() == oldComponentId); + + Assert.Equal(originalExcludeFromPdfArray!.Count - 1, excludeFromPdfArray.Count); } [Theory] @@ -115,17 +115,18 @@ public async Task SaveFormLayoutWithMultipleComponentIdsChange_ShouldSyncOccurre Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json) }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string layoutSettingsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/ui/{layoutSetName}/Settings.json"); JsonNode layoutSettings = JsonNode.Parse(layoutSettingsFromRepo); JsonArray excludeFromPdfArray = layoutSettings["components"]?["excludeFromPdf"]?.AsArray(); - excludeFromPdfArray.Where(node => node.GetValue<string>() == oldComponentId).Should().BeEmpty(); - excludeFromPdfArray.Where(node => node.GetValue<string>() == oldComponentId2).Should().BeEmpty(); - excludeFromPdfArray.Should().ContainSingle(node => node.GetValue<string>() == newComponentId); - excludeFromPdfArray.Should().ContainSingle(node => node.GetValue<string>() == newComponentId2); + Assert.DoesNotContain(excludeFromPdfArray, node => node.GetValue<string>() == oldComponentId); + Assert.DoesNotContain(excludeFromPdfArray, node => node.GetValue<string>() == oldComponentId2); + + Assert.Single(excludeFromPdfArray, node => node.GetValue<string>() == newComponentId); + Assert.Single(excludeFromPdfArray, node => node.GetValue<string>() == newComponentId2); } private string ArrangeApiRequestContent(List<ComponentIdChange> componentIdsChanges) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetAppMetadataModelIdsTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetAppMetadataModelIdsTests.cs index fbaf58c0b50..79f5a9db723 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetAppMetadataModelIdsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetAppMetadataModelIdsTests.cs @@ -3,9 +3,7 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; -using SharedResources.Tests; using Xunit; namespace Designer.Tests.Controllers.AppDevelopmentController @@ -30,11 +28,11 @@ public async Task GetAppMetadataModelIds_Should_Return_ModelIdsList(string org, using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); - responseContent.Should().Be("[\"datamodel\",\"unUsedDatamodel\",\"HvemErHvem_M\"]"); + Assert.Equal("[\"datamodel\",\"unUsedDatamodel\",\"HvemErHvem_M\"]", responseContent); } [Theory] @@ -48,11 +46,11 @@ public async Task GetAppMetadataModelIds_NoModelsInAppMetadata_Should_Return_Emp using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); - responseContent.Should().Be("[]"); + Assert.Equal("[]", responseContent); } } } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetAppVersionTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetAppVersionTests.cs index 1782fcff6db..841ac5748ba 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetAppVersionTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetAppVersionTests.cs @@ -6,7 +6,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -42,12 +41,12 @@ public async Task GetAppVersion_GivenCsProjFile_ShouldReturnOK(string org, strin string url = VersionPrefix(org, targetRepository); using var response = await HttpClient.GetAsync(url); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); var responseVersion = await response.Content.ReadAsAsync<VersionResponse>(); - responseVersion.BackendVersion.ToString().Should().Be(backendVersion); - responseVersion.FrontendVersion.Should().Be(frontendVersion); + Assert.Equal(backendVersion, responseVersion.BackendVersion.ToString()); + Assert.Equal(frontendVersion, responseVersion.FrontendVersion); } [Theory] @@ -61,7 +60,7 @@ public async Task GetAppVersion_GivenCsprojFileWithoutAppLib_ShouldReturn404(str string url = VersionPrefix(org, targetRepository); using var response = await HttpClient.GetAsync(url); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Theory] @@ -71,7 +70,7 @@ public async Task GetAppVersion_NotGivenCsprojFile_ShouldReturn404(string org, s string url = VersionPrefix(org, app); using var response = await HttpClient.GetAsync(url); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Theory] @@ -85,9 +84,9 @@ await AddCsProjToRepo("App/App.csproj", csprojTemplate, string url = VersionPrefix(org, targetRepository); using var response = await HttpClient.GetAsync(url); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); var responseVersion = await response.Content.ReadAsAsync<VersionResponse>(); - responseVersion.FrontendVersion.Should().BeNull(); + Assert.Null(responseVersion.FrontendVersion); } [Theory] @@ -102,9 +101,9 @@ await AddCsProjToRepo("App/App.csproj", csprojTemplate, string url = VersionPrefix(org, targetRepository); using var response = await HttpClient.GetAsync(url); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); var responseVersion = await response.Content.ReadAsAsync<VersionResponse>(); - responseVersion.FrontendVersion.Should().BeNull(); + Assert.Null(responseVersion.FrontendVersion); } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetFormLayoutsTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetFormLayoutsTests.cs index 975e85c47b4..1b0fdf86aaf 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetFormLayoutsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetFormLayoutsTests.cs @@ -8,7 +8,6 @@ using Designer.Tests.Controllers.ApiTests; using Designer.Tests.TestDataClasses; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -35,7 +34,7 @@ public async Task GetAppDevelopment_ShouldReturnLayouts(string org, string app, using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); var responseJson = JsonNode.Parse(responseContent); @@ -43,7 +42,7 @@ public async Task GetAppDevelopment_ShouldReturnLayouts(string org, string app, foreach ((string expectedLayoutName, string expectedLayout) in expectedLayouts) { string actualLayout = responseJson[Path.GetFileNameWithoutExtension(expectedLayoutName)].ToJsonString(); - JsonUtils.DeepEquals(expectedLayout, actualLayout).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedLayout, actualLayout)); } } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetLayoutSetsTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetLayoutSetsTests.cs index 45b7a69fe41..a6a0ccafbac 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetLayoutSetsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetLayoutSetsTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -32,16 +31,16 @@ public async Task GetLayoutSets_ShouldReturnLayoutSets(string org, string app, s using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = string.IsNullOrEmpty(layoutSetName) ? null : await response.Content.ReadAsStringAsync(); if (string.IsNullOrEmpty(layoutSetName)) { - responseContent.Should().BeNull(); + Assert.Null(responseContent); } else { - JsonUtils.DeepEquals(expectedLayoutSets, responseContent).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedLayoutSets, responseContent)); } } @@ -54,7 +53,7 @@ public async Task GetLayoutSettings_IfNotExists_Should_AndReturnNotFound(string using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } private async Task<string> AddLayoutSetsToRepo(string createdFolderPath, string expectedLayoutSetsPath) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetLayoutSettingsTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetLayoutSettingsTests.cs index c9dbdca5d2b..931e518cd28 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetLayoutSettingsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetLayoutSettingsTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -36,10 +35,10 @@ public async Task GetLayoutSettings_ShouldReturnLayouts(string org, string app, using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedLayoutSettings, responseContent).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedLayoutSettings, responseContent)); } [Theory(Skip = "If App/ui is not present in repo, the controller returns 500")] @@ -51,7 +50,7 @@ public async Task GetLayoutSettings_IfNotExists_Should_AndReturnNotFound(string using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } private async Task<string> AddLayoutSettingsToRepo(string createdFolderPath, string layoutSetName, string expectedLayoutPath) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetModelMetadataTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetModelMetadataTests.cs index 80c532658bb..72b65767865 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetModelMetadataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetModelMetadataTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -33,9 +32,9 @@ public async Task GetModelMetadata_Should_Return_ModelMetadata_Based_On_LayoutSe string responseContent = await response.Content.ReadAsStringAsync(); // Assert - response.StatusCode.Should().Be(HttpStatusCode.OK); - responseContent.Should().Be(expectedModelMetadata); - JsonUtils.DeepEquals(expectedModelMetadata, responseContent).Should().BeTrue(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedModelMetadata, responseContent); + Assert.True(JsonUtils.DeepEquals(expectedModelMetadata, responseContent)); } [Theory] @@ -52,9 +51,9 @@ public async Task GetModelMetadata_Should_Return_ModelMetadata_When_DataModelNam string responseContent = await response.Content.ReadAsStringAsync(); // Assert - response.StatusCode.Should().Be(HttpStatusCode.OK); - responseContent.Should().Be(expectedModelMetadata); - JsonUtils.DeepEquals(expectedModelMetadata, responseContent).Should().BeTrue(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedModelMetadata, responseContent); + Assert.True(JsonUtils.DeepEquals(expectedModelMetadata, responseContent)); } [Theory] @@ -71,7 +70,7 @@ public async Task GetModelMetadata_Should_Return_404_When_No_Corresponding_Datam using var response = await HttpClient.SendAsync(httpRequestMessage); // Assert - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } private async Task<string> AddModelMetadataToRepo(string createdFolderPath, string expectedModelMetadataPath) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetOptionListIdsTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetOptionListIdsTests.cs deleted file mode 100644 index c5b77263ef4..00000000000 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetOptionListIdsTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Designer.Tests.Controllers.ApiTests; -using Designer.Tests.Utils; -using FluentAssertions; -using Microsoft.AspNetCore.Mvc.Testing; -using Xunit; - -namespace Designer.Tests.Controllers.AppDevelopmentController -{ - public class GetOptionListIdsTests : DesignerEndpointsTestsBase<GetOptionListIdsTests>, IClassFixture<WebApplicationFactory<Program>> - { - private static string VersionPrefix(string org, string repository) => $"/designer/api/{org}/{repository}/app-development"; - public GetOptionListIdsTests(WebApplicationFactory<Program> factory) : base(factory) - { - } - - [Theory] - [InlineData("ttd", "app-with-options", "testUser")] - public async Task GetOptionsListIds_ShouldReturnOk(string org, string app, string developer) - { - string targetRepository = TestDataHelper.GenerateTestRepoName(); - await CopyRepositoryForTest(org, app, developer, targetRepository); - - var expectedOptionsListIds = new List<string>() - { - { "other-options" }, - { "test-options" }, - { "options-with-null-fields" }, - }; - - string url = $"{VersionPrefix(org, targetRepository)}/option-list-ids"; - using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); - - using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); - - string responseContent = await response.Content.ReadAsStringAsync(); - List<string> responseList = JsonSerializer.Deserialize<List<string>>(responseContent); - responseList.Count.Should().Be(3); - foreach (string id in expectedOptionsListIds) - { - responseList.Should().Contain(id); - } - } - - [Theory] - [InlineData("ttd", "empty-app")] - public async Task GetOptionsListIds_WhenNotExists_ReturnsNotFound(string org, string app) - { - string url = $"{VersionPrefix(org, app)}/option-list-ids"; - using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); - - using var response = await HttpClient.SendAsync(httpRequestMessage); - - response.StatusCode.Should().Be(HttpStatusCode.NoContent); - } - } -} diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetRuleConfigTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetRuleConfigTests.cs index a55e9a3da5c..ae32f2e02e9 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetRuleConfigTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetRuleConfigTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -33,10 +32,10 @@ public async Task GetRuleConfig_ShouldReturnOK(string org, string app, string de using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedRuleConfig, responseContent).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedRuleConfig, responseContent)); } [Theory] @@ -48,7 +47,7 @@ public async Task GetRuleConfig_WhenNotExists_ReturnsNotFound(string org, string using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } [Theory] @@ -65,10 +64,10 @@ public async Task GetRuleConfig_WhenFileMissesDataOnRoot_ReturnsFixedFile(string using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedRuleConfig, responseContent).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedRuleConfig, responseContent)); } private async Task<string> AddRuleConfigToRepo(string createdFolderPath, string layoutSetName, string expectedLayoutPath) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetRuleHandlerTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetRuleHandlerTests.cs index 3cdff3ef0a9..58f5a8d9bfd 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetRuleHandlerTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/GetRuleHandlerTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -36,10 +35,10 @@ public async Task GetRuleHandler_ShouldReturnJsContent(string org, string app, s using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); - expectedRuleHandler.Should().Be(responseContent); + Assert.Equal(expectedRuleHandler, responseContent); } [Theory] @@ -51,7 +50,7 @@ public async Task GetRuleHandler_IfNotExists_Should_AndReturnNotFound(string org using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } private static async Task<string> AddRuleHandler(string createdFolderPath, string layoutSetName, string expectedRuleHandlerPath) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveFormLayoutTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveFormLayoutTests.cs index 496f39223fb..ae9ee078025 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveFormLayoutTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveFormLayoutTests.cs @@ -1,4 +1,6 @@ -using System.Net; +using System; +using System.Linq; +using System.Net; using System.Net.Http; using System.Net.Mime; using System.Text; @@ -9,7 +11,6 @@ using Altinn.Studio.Designer.Models; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -53,13 +54,13 @@ public async Task SaveFormLayout_ReturnsOk(string org, string app, string develo ["layout"] = JsonNode.Parse(layout) }; HttpResponseMessage response = await SendHttpRequest(url, payload); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string relativePath = string.IsNullOrEmpty(layoutSetName) ? $"App/ui/layouts/{layoutName}.json" : $"App/ui/{layoutSetName}/layouts/{layoutName}.json"; string savedLayout = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, relativePath); - JsonUtils.DeepEquals(layout, savedLayout).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(layout, savedLayout)); } [Theory] @@ -84,13 +85,57 @@ public async Task SaveFormLayoutWithComponentIdsChange_ReturnsOk(string org, str ["layout"] = JsonNode.Parse(layout) }; HttpResponseMessage response = await SendHttpRequest(url, payload); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string relativePath = string.IsNullOrEmpty(layoutSetName) ? $"App/ui/layouts/{layoutName}.json" : $"App/ui/{layoutSetName}/layouts/{layoutName}.json"; string savedLayout = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, relativePath); - JsonUtils.DeepEquals(layout, savedLayout).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(layout, savedLayout)); + } + + [Theory] + [InlineData("ttd", "testUser", "component", "Side2", "Input-Om7N3y")] + public async Task SaveFormLayoutWithDeletedComponent_DeletesAssociatedSummary2Components_ReturnsOk(string org, string developer, string layoutSetName, string layoutName, string componentId) + { + string actualApp = "app-with-summary2-components"; + string app = TestDataHelper.GenerateTestRepoName(); + await CopyRepositoryForTest(org, actualApp, developer, app); + + string layout = TestDataHelper.GetFileFromRepo(org, app, developer, $"App/ui/{layoutSetName}/layouts/{layoutName}.json"); + JsonNode layoutWithDeletedComponent = JsonNode.Parse(layout); + JsonArray layoutArray = layoutWithDeletedComponent["data"]["layout"] as JsonArray; + layoutArray?.RemoveAt(0); + + string url = $"{VersionPrefix(org, app)}/form-layout/{layoutName}?layoutSetName={layoutSetName}"; + var payload = new JsonObject + { + ["componentIdsChange"] = new JsonArray() { + new JsonObject + { + ["oldComponentId"] = componentId, + } + }, + ["layout"] = layoutWithDeletedComponent + }; + HttpResponseMessage response = await SendHttpRequest(url, payload); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + string expectedApp = "app-with-summary2-components-after-deleting-references"; + + string[] layoutPaths = [ + "component/layouts/Side1.json", + "component/layouts/Side2.json", + "component2/layouts/Side1.json", + "component2/layouts/Side2.json" + ]; + + layoutPaths.ToList().ForEach(file => + { + string actual = TestDataHelper.GetFileFromRepo(org, app, developer, $"App/ui/{file}"); + string expected = TestDataHelper.GetFileFromRepo(org, expectedApp, developer, $"App/ui/{file}"); + Assert.True(JsonUtils.DeepEquals(actual, expected)); + }); } [Theory] @@ -102,11 +147,11 @@ public async Task SaveFormLayoutWithNewPageLanguageUpdate_ReturnsOk(string org, string url = $"{VersionPrefix(org, targetRepository)}/form-layout/{layoutName}?layoutSetName={layoutSetName}"; string layout = SharedResourcesHelper.LoadTestDataAsString(layoutPath); - TestDataHelper.FileExistsInRepo(org, targetRepository, developer, "App/config/texts/resource.nb.json").Should().BeTrue(); + Assert.True(TestDataHelper.FileExistsInRepo(org, targetRepository, developer, "App/config/texts/resource.nb.json")); string file = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/texts/resource.nb.json"); TextResource textResource = JsonSerializer.Deserialize<TextResource>(file, s_jsonOptions); - textResource.Resources.Should().NotContain(x => x.Id == "next"); - textResource.Resources.Should().NotContain(x => x.Id == "back"); + Assert.DoesNotContain(textResource.Resources, x => x.Id == "next"); + Assert.DoesNotContain(textResource.Resources, x => x.Id == "back"); var payload = new JsonObject { @@ -114,12 +159,12 @@ public async Task SaveFormLayoutWithNewPageLanguageUpdate_ReturnsOk(string org, ["layout"] = JsonNode.Parse(layout) }; HttpResponseMessage response = await SendHttpRequest(url, payload); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string newTextFile = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/config/texts/resource.nb.json"); TextResource newTextResource = JsonSerializer.Deserialize<TextResource>(newTextFile, s_jsonOptions); - newTextResource.Resources.Should().ContainSingle(x => x.Id == "next"); - newTextResource.Resources.Should().ContainSingle(x => x.Id == "back"); + Assert.Single(newTextResource.Resources, x => x.Id == "next"); + Assert.Single(newTextResource.Resources, x => x.Id == "back"); } private async Task<HttpResponseMessage> SendHttpRequest(string url, JsonObject payload) diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveLayoutSettingsTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveLayoutSettingsTests.cs index b6961b890b1..3565c7a9d86 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveLayoutSettingsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveLayoutSettingsTests.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -44,13 +43,13 @@ public async Task SaveLayoutSettings_ReturnsOk(string org, string app, string de }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string relativePath = string.IsNullOrEmpty(layoutSetName) ? "App/ui/Settings.json" : $"App/ui/{layoutSetName}/Settings.json"; string savedLayoutSettings = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, relativePath); - JsonUtils.DeepEquals(layoutSettings, savedLayoutSettings).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(layoutSettings, savedLayoutSettings)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveRuleConfigTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveRuleConfigTests.cs index 4b9f94ef77b..c193bacba0a 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveRuleConfigTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveRuleConfigTests.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -39,13 +38,13 @@ public async Task SaveRuleConfiguration_ShouldCreateRuleConfigurationFile_AndRet }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string relativePath = string.IsNullOrEmpty(layoutSetName) ? "App/ui/RuleConfiguration.json" : $"App/ui/{layoutSetName}/RuleConfiguration.json"; string savedFile = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, relativePath); - JsonUtils.DeepEquals(content, savedFile).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(content, savedFile)); } } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveRuleHandlerTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveRuleHandlerTests.cs index 605e3eccda3..f7c7e603064 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveRuleHandlerTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/SaveRuleHandlerTests.cs @@ -7,7 +7,6 @@ using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Mocks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using SharedResources.Tests; @@ -45,12 +44,12 @@ public async Task SaveRuleHandler_ShouldCreateRuleHandlerFile_AndReturnNoContent }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); string relativePath = string.IsNullOrEmpty(layoutSetName) ? "App/ui/RuleHandler.js" : $"App/ui/{layoutSetName}/RuleHandler.js"; - TestDataHelper.GetFileFromRepo(org, targetRepository, developer, relativePath).Should().BeEquivalentTo(content); + Assert.Equal(TestDataHelper.GetFileFromRepo(org, targetRepository, developer, relativePath), content); } } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/UpdateFormLayoutNameTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/UpdateFormLayoutNameTests.cs index a14d0f12964..b9bee6abc81 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/UpdateFormLayoutNameTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/UpdateFormLayoutNameTests.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -37,7 +36,7 @@ public async Task UpdateFormLayoutName_Change_FileName_And_ReturnsOk(string org, }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string relativeOldLayoutPath = string.IsNullOrEmpty(layoutSetName) ? $"App/ui/layouts/{layoutName}.json" @@ -47,8 +46,8 @@ public async Task UpdateFormLayoutName_Change_FileName_And_ReturnsOk(string org, : $"App/ui/{layoutSetName}/layouts/{newLayoutName}.json"; string oldLayoutPath = Path.Combine(TestRepoPath, relativeOldLayoutPath); string newLayoutPath = Path.Combine(TestRepoPath, relativeNewLayoutPath); - File.Exists(oldLayoutPath).Should().BeFalse(); - File.Exists(newLayoutPath).Should().BeTrue(); + Assert.False(File.Exists(oldLayoutPath)); + Assert.True(File.Exists(newLayoutPath)); } [Theory] @@ -66,7 +65,7 @@ public async Task UpdateFormLayoutName_NonExistingName_ShouldReturnNotFound(stri }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } } diff --git a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/UpdateLayoutSetNameTests.cs b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/UpdateLayoutSetNameTests.cs index f1e72f245b4..2ac700a000b 100644 --- a/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/UpdateLayoutSetNameTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppDevelopmentController/UpdateLayoutSetNameTests.cs @@ -10,7 +10,6 @@ using Altinn.Studio.Designer.Models; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -41,14 +40,14 @@ public async Task UpdateLayoutSetName_ReturnsOk(string org, string app, string d }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); LayoutSets layoutSetsAfter = await GetLayoutSetsFile(org, targetRepository, developer); - layoutSetsBefore.Schema.Should().NotBeNull(); + Assert.NotNull(layoutSetsBefore.Schema); Assert.False(layoutSetsBefore.Sets.Exists(set => set.Id == newLayoutSetName)); - layoutSetsBefore.Sets.Should().HaveCount(layoutSetsAfter.Sets.Count); - layoutSetsAfter.Schema.Should().NotBeNull(); + Assert.Equal(layoutSetsAfter.Sets.Count, layoutSetsBefore.Sets.Count); + Assert.NotNull(layoutSetsAfter.Schema); Assert.True(layoutSetsAfter.Sets.Exists(set => set.Id == newLayoutSetName)); } @@ -68,7 +67,7 @@ public async Task UpdateLayoutSetName_NewLayoutSetNameExistsBefore_ReturnsBadReq }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); Dictionary<string, string> responseMessage = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent); Assert.Equal($"Layout set name, {existingLayoutSetName}, already exists.", responseMessage["infoMessage"]); @@ -91,7 +90,7 @@ public async Task UpdateLayoutSet_NewLayoutSetNameIsEmpty_ReturnsBadRequest(stri }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Theory] @@ -111,7 +110,7 @@ public async Task UpdateLayoutSet_NewLayoutSetNameDoesNotMatchRegex_ReturnsBadRe }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Theory] @@ -131,7 +130,7 @@ public async Task UpdateLayoutSetName_AppWithoutLayoutSets_ReturnsNotFound(strin }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } private async Task<LayoutSets> GetLayoutSetsFile(string org, string app, string developer) diff --git a/backend/tests/Designer.Tests/Controllers/AppScopesController/GetAppScopesTests.cs b/backend/tests/Designer.Tests/Controllers/AppScopesController/GetAppScopesTests.cs index 04c9764b761..0591eb7af99 100644 --- a/backend/tests/Designer.Tests/Controllers/AppScopesController/GetAppScopesTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppScopesController/GetAppScopesTests.cs @@ -5,7 +5,6 @@ using Designer.Tests.Controllers.AppScopesController.Base; using Designer.Tests.DbIntegrationTests; using Designer.Tests.Fixtures; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -28,10 +27,10 @@ public async Task GetAppScopes_Should_ReturnOk_WithEmptyScopes_IfRecordDoesntExi , VersionPrefix(org, app)); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); AppScopesResponse repsponseContent = await response.Content.ReadAsAsync<AppScopesResponse>(); - repsponseContent.Scopes.Should().BeEmpty(); + Assert.Empty(repsponseContent.Scopes); } [Theory] @@ -45,14 +44,14 @@ public async Task GetAppScopes_Should_ReturnOk_WithScopes_IfRecordExists(string , VersionPrefix(org, app)); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); AppScopesResponse responseContent = await response.Content.ReadAsAsync<AppScopesResponse>(); - responseContent.Scopes.Should().HaveCount(4); + Assert.Equal(4, responseContent.Scopes.Count); foreach (MaskinPortenScopeDto scope in responseContent.Scopes) { - entity.Scopes.Should().Contain(x => scope.Scope == x.Scope && scope.Description == x.Description); + Assert.Contains(entity.Scopes, x => x.Scope == scope.Scope && x.Description == scope.Description); } } } diff --git a/backend/tests/Designer.Tests/Controllers/AppScopesController/GetScopesFromMaskinPortenTests.cs b/backend/tests/Designer.Tests/Controllers/AppScopesController/GetScopesFromMaskinPortenTests.cs index 1068af40222..9fb5ac53512 100644 --- a/backend/tests/Designer.Tests/Controllers/AppScopesController/GetScopesFromMaskinPortenTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppScopesController/GetScopesFromMaskinPortenTests.cs @@ -7,7 +7,6 @@ using Designer.Tests.Controllers.AppScopesController.Base; using Designer.Tests.Controllers.AppScopesController.Utils; using Designer.Tests.Fixtures; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -43,11 +42,11 @@ public async Task GetScopesFromMaskinPortens_Should_ReturnOk(string org, string , VersionPrefix(org, app)); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); AppScopesResponse repsponseContent = await response.Content.ReadAsAsync<AppScopesResponse>(); JsonArray array = (JsonArray)JsonNode.Parse(maskinPortenResponse); - repsponseContent.Scopes.Count.Should().Be(array.Count); + Assert.Equal(array.Count, repsponseContent.Scopes.Count); } diff --git a/backend/tests/Designer.Tests/Controllers/AppScopesController/UpsertAppScopesTests.cs b/backend/tests/Designer.Tests/Controllers/AppScopesController/UpsertAppScopesTests.cs index 3b02189030f..788987128fb 100644 --- a/backend/tests/Designer.Tests/Controllers/AppScopesController/UpsertAppScopesTests.cs +++ b/backend/tests/Designer.Tests/Controllers/AppScopesController/UpsertAppScopesTests.cs @@ -11,7 +11,6 @@ using Designer.Tests.DbIntegrationTests; using Designer.Tests.Fixtures; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Xunit; @@ -52,17 +51,17 @@ private async Task CallUpsertEndpointAndAssertFromDb(string org, string app, App httpRequestMessage.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); var dbEntity = await DesignerDbFixture.DbContext.AppScopes.SingleAsync(x => x.App == app && x.Org == org); - dbEntity.Should().NotBeNull(); + Assert.NotNull(dbEntity); var scopes = JsonSerializer.Deserialize<ISet<MaskinPortenScopeEntity>>(dbEntity.Scopes, JsonSerializerOptions); - scopes.Should().HaveCount(payload.Scopes.Count); + Assert.Equal(payload.Scopes.Count, scopes.Count); foreach (MaskinPortenScopeEntity maskinPortenScopeEntity in scopes) { - payload.Scopes.Should().Contain(x => x.Scope == maskinPortenScopeEntity.Scope && x.Description == maskinPortenScopeEntity.Description); + Assert.Contains(payload.Scopes, x => x.Scope == maskinPortenScopeEntity.Scope && x.Description == maskinPortenScopeEntity.Description); } } diff --git a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/AddMetadataForAttachmentTests.cs b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/AddMetadataForAttachmentTests.cs index 2cbf3509fcf..f18d7e97bc6 100644 --- a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/AddMetadataForAttachmentTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/AddMetadataForAttachmentTests.cs @@ -11,7 +11,6 @@ using Altinn.Studio.Designer.Models.App; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -37,15 +36,16 @@ public async Task AddMetadataForAttachment_WhenExists_ShouldReturnConflict(strin using var payloadContent = new StringContent(JsonSerializer.Serialize(payload, JsonSerializerOptions), Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await HttpClient.PostAsync(url, payloadContent); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string applicationMetadataFile = await File.ReadAllTextAsync(Path.Combine(TestRepoPath, "App", "config", "applicationmetadata.json")); var applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataFile, JsonSerializerOptions); var attachmentDataType = applicationMetadata.DataTypes.Single(x => x.Id == payload.Id); - attachmentDataType.MaxCount.Should().Be(payload.MaxCount); - attachmentDataType.MaxSize.Should().Be(payload.MaxSize); - attachmentDataType.MinCount.Should().Be(payload.MinCount); + + Assert.Equal(payload.MaxCount, attachmentDataType.MaxCount); + Assert.Equal(payload.MaxSize, attachmentDataType.MaxSize); + Assert.Equal(payload.MinCount, attachmentDataType.MinCount); } /// <summary> diff --git a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/CreateApplicationMetadataTests.cs b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/CreateApplicationMetadataTests.cs index 63195a92852..75d928660de 100644 --- a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/CreateApplicationMetadataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/CreateApplicationMetadataTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -29,7 +28,7 @@ public async Task CreateApplicationMetadata_WhenExists_ShouldReturnConflict(stri using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url); using var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.Conflict); + Assert.Equal(HttpStatusCode.Conflict, response.StatusCode); } [Theory] @@ -46,7 +45,7 @@ public async Task CreateApplicationMetadata_ShouldReturnOK(string org, string ap using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url); using var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); } } diff --git a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/DeleteMetadataForAttachmentTests.cs b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/DeleteMetadataForAttachmentTests.cs index 3d58567046f..512f66738c7 100644 --- a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/DeleteMetadataForAttachmentTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/DeleteMetadataForAttachmentTests.cs @@ -7,7 +7,6 @@ using Altinn.Studio.Designer.Models.App; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -36,7 +35,7 @@ public async Task DeleteMetadataForAttachment_WhenExists_ShouldReturnOk(string o var response = await HttpClient.SendAsync(requestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string currentMetadata = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); ApplicationMetadata applicationMetadataAfterDelete = JsonSerializer.Deserialize<ApplicationMetadata>(currentMetadata, JsonSerializerOptions); Assert.DoesNotContain(applicationMetadataAfterDelete.DataTypes, x => x.Id == attacmentIdToDelete); diff --git a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/GetApplicationMetadataTests.cs b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/GetApplicationMetadataTests.cs index 9362d2a9966..59c3ab3469a 100644 --- a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/GetApplicationMetadataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/GetApplicationMetadataTests.cs @@ -5,7 +5,6 @@ using Altinn.Studio.Designer.Models.App; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -34,10 +33,10 @@ public async Task GetApplicationMetadata_ShouldReturnOK(string org, string app, string url = VersionPrefix(org, targetRepository); var response = await HttpClient.GetAsync(url); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); string expectedJson = JsonSerializer.Serialize(JsonSerializer.Deserialize<ApplicationMetadata>(metadataFile, JsonSerializerOptions), JsonSerializerOptions); - JsonUtils.DeepEquals(expectedJson, responseContent).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedJson, responseContent)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/UpdateApplicationMetadataTests.cs b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/UpdateApplicationMetadataTests.cs index 4681cc200fe..6d51148286b 100644 --- a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/UpdateApplicationMetadataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/UpdateApplicationMetadataTests.cs @@ -7,7 +7,6 @@ using Altinn.Studio.Designer.Models.App; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -35,11 +34,11 @@ public async Task UpdateApplicationMetadata_WhenExists_ShouldReturnConflict(stri using var response = await HttpClient.PutAsync(url, new StringContent(metadata, Encoding.UTF8, MediaTypeNames.Application.Json)); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedMetadataJson, responseContent).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedMetadataJson, responseContent)); string fileFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); - JsonUtils.DeepEquals(expectedMetadataJson, fileFromRepo).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedMetadataJson, fileFromRepo)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/UpdateMetadataForAttachmentTests.cs b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/UpdateMetadataForAttachmentTests.cs index ba04ee43195..e111a9d6e74 100644 --- a/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/UpdateMetadataForAttachmentTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ApplicationMetadataController/UpdateMetadataForAttachmentTests.cs @@ -11,7 +11,6 @@ using Altinn.Studio.Designer.Models.App; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -43,21 +42,23 @@ public async Task UpdateMetadataForAttachment_WhenExists_ShouldReturnConflict(st using var payloadContent = new StringContent(payload, Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await HttpClient.PutAsync(url, payloadContent); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string applicationMetadataFile = await File.ReadAllTextAsync(Path.Combine(TestRepoPath, "App", "config", "applicationmetadata.json")); var applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataFile, _jsonSerializerOptions); var attachmentDataType = applicationMetadata.DataTypes.Single(x => x.Id == payloadNode!["id"]!.ToString()); - attachmentDataType.MaxCount.Should().Be(payloadNode!["maxCount"]!.GetValue<int>()); - attachmentDataType.MaxSize.Should().Be(payloadNode!["maxSize"]!.GetValue<int>()); - attachmentDataType.MinCount.Should().Be(payloadNode!["minCount"]!.GetValue<int>()); - attachmentDataType.AllowedContentTypes.Count.Should().Be(expectedContentTypes.Length); + Assert.Equal(attachmentDataType.MaxCount, payloadNode!["maxCount"]!.GetValue<int>()); + Assert.Equal(attachmentDataType.MaxSize, payloadNode!["maxSize"]!.GetValue<int>()); + Assert.Equal(attachmentDataType.MinCount, payloadNode!["minCount"]!.GetValue<int>()); + + + Assert.Equal(attachmentDataType.AllowedContentTypes.Count, expectedContentTypes.Length); foreach (string contentType in expectedContentTypes) { - attachmentDataType.AllowedContentTypes.Should().Contain(contentType); + Assert.Contains(contentType, attachmentDataType.AllowedContentTypes); } } diff --git a/backend/tests/Designer.Tests/Controllers/ContactController/FetchBelongsToOrgTests.cs b/backend/tests/Designer.Tests/Controllers/ContactController/FetchBelongsToOrgTests.cs new file mode 100644 index 00000000000..f71d47b2c41 --- /dev/null +++ b/backend/tests/Designer.Tests/Controllers/ContactController/FetchBelongsToOrgTests.cs @@ -0,0 +1,66 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Designer.Tests.Controllers.ApiTests; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Designer.Tests.Controllers.ContactController; + +public class FetchBelongsToOrgTests : DesignerEndpointsTestsBase<FetchBelongsToOrgTests>, + IClassFixture<WebApplicationFactory<Program>> +{ + public FetchBelongsToOrgTests(WebApplicationFactory<Program> factory) : base(factory) + { + } + + [Fact] + public async Task UsersThatBelongsToOrg_ShouldReturn_True() + { + string url = "/designer/api/contact/belongs-to-org"; + + using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); + + var response = await HttpClient.SendAsync(httpRequestMessage); + var responseContent = await response.Content.ReadAsAsync<BelongsToOrgDto>(); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(responseContent.BelongsToOrg); + } + + [Fact] + public async Task UsersThatDoNotBelongsToOrg_ShouldReturn_False_IfAnonymousUser() + { + string configPath = GetConfigPath(); + IConfiguration configuration = new ConfigurationBuilder() + .AddJsonFile(configPath, false, false) + .AddJsonStream(GenerateJsonOverrideConfig()) + .AddEnvironmentVariables() + .Build(); + + var anonymousClient = Factory.WithWebHostBuilder(builder => + { + builder.UseConfiguration(configuration); + builder.ConfigureTestServices(services => + { + services.AddAuthentication("Anonymous") + .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Anonymous", options => { }); + }); + }).CreateDefaultClient(); + + string url = "/designer/api/contact/belongs-to-org"; + + using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); + + var response = await anonymousClient.SendAsync(httpRequestMessage); + var responseContent = await response.Content.ReadAsAsync<BelongsToOrgDto>(); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.False(responseContent.BelongsToOrg); + } +} diff --git a/backend/tests/Designer.Tests/Controllers/DataModelsController/CsharpNamespaceTests.cs b/backend/tests/Designer.Tests/Controllers/DataModelsController/CsharpNamespaceTests.cs index fdd1864c73d..4928e81928e 100644 --- a/backend/tests/Designer.Tests/Controllers/DataModelsController/CsharpNamespaceTests.cs +++ b/backend/tests/Designer.Tests/Controllers/DataModelsController/CsharpNamespaceTests.cs @@ -16,7 +16,6 @@ using Altinn.Studio.Designer.ViewModels.Request; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; @@ -46,16 +45,14 @@ public async Task Given_XsdUploaded_ShouldProduce_CorrectNamespace(string xsdPat // get the csharp model from repo string csharpModel = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, expectedModelPath); - Regex.Match(csharpModel, $"^namespace {expectedNamespace}$".Replace(".", "\\."), RegexOptions.Multiline).Success.Should().BeTrue(); + Assert.True(Regex.Match(csharpModel, $"^namespace {expectedNamespace}$".Replace(".", "\\."), + RegexOptions.Multiline).Success); string applicationMetadataContent = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); var applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataContent, JsonSerializerOptions); - applicationMetadata.DataTypes.Should().Contain(x => x.AppLogic != null && x.AppLogic.ClassRef == - $"{expectedNamespace}.{expectedTypeName}"); - - applicationMetadata.DataTypes.Should().NotContain(x => x.AppLogic != null && x.AppLogic.ClassRef == - $"{notExpectedNamespace}.{expectedTypeName}"); + Assert.Contains(applicationMetadata.DataTypes, x => x.AppLogic != null && x.AppLogic.ClassRef == $"{expectedNamespace}.{expectedTypeName}"); + Assert.DoesNotContain(applicationMetadata.DataTypes, x => x.AppLogic != null && x.AppLogic.ClassRef == $"{notExpectedNamespace}.{expectedTypeName}"); } [Theory] @@ -71,14 +68,14 @@ public async Task Given_XsdUploaded_NewUploadWith_Same_ModelName_ShouldReturnVal Assert.Equal(HttpStatusCode.Created, setupResponse.StatusCode); using var response = await UploadNewXsdSchema(xsdPath, url); - response.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode); var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(await response.Content.ReadAsStringAsync()); - problemDetails.Should().NotBeNull(); + Assert.NotNull(problemDetails); JsonElement errorCode = (JsonElement)problemDetails.Extensions[ProblemDetailsExtensionsCodes.ErrorCode]; - errorCode.ToString().Should().Be(expectedErrorCode); + Assert.Equal(expectedErrorCode, errorCode.ToString()); } [Theory] @@ -93,7 +90,7 @@ public async Task Given_XsdUpload_When_ReUploaded_ShouldReturn_201(string xsdPat Assert.Equal(HttpStatusCode.Created, setupResponse.StatusCode); using var response = await UploadNewXsdSchema(xsdPath, url); - response.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); } @@ -107,19 +104,21 @@ public async Task Given_JsonSchemaSent_ShouldProduce_CorrectNamespacePut(string string url = $"{VersionPrefix(org, targetRepo)}/datamodel?modelPath={modelPath}"; using var response = await GenerateModels(expectedModelName, url); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); // get the csharp model from repo string csharpModel = TestDataHelper.GetFileFromRepo(org, targetRepo, developer, expectedModelPath); - Regex.Match(csharpModel, $"^namespace {expectedNamespace}$".Replace(".", "\\."), RegexOptions.Multiline).Success.Should().BeTrue(); + Assert.True(Regex.Match(csharpModel, $"^namespace {expectedNamespace}$".Replace(".", "\\."), + RegexOptions.Multiline).Success); string applicationMetadataContent = TestDataHelper.GetFileFromRepo(org, targetRepo, developer, "App/config/applicationmetadata.json"); var applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataContent, JsonSerializerOptions); - applicationMetadata.DataTypes.Should().Contain(x => x.AppLogic != null && x.AppLogic.ClassRef == + Assert.Contains(applicationMetadata.DataTypes, x => x.AppLogic != null && x.AppLogic.ClassRef == $"{expectedNamespace}.{expectedModelName}"); - applicationMetadata.DataTypes.Should().NotContain(x => x.AppLogic != null && x.AppLogic.ClassRef == + Assert.DoesNotContain(applicationMetadata.DataTypes, x => x.AppLogic != null && x.AppLogic.ClassRef == $"{notExpectedNamespace}.{expectedModelName}"); + } @@ -137,14 +136,14 @@ public async Task Given_RepoPreparedWithXsd_When_JsonSchemaCreated_WithSameModel var response = await GenerateNewJsonSchema(modelName, $"{VersionPrefix(org, targetRepository)}/new"); - response.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode); var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(await response.Content.ReadAsStringAsync()); - problemDetails.Should().NotBeNull(); + Assert.NotNull(problemDetails); JsonElement errorCode = (JsonElement)problemDetails.Extensions[ProblemDetailsExtensionsCodes.ErrorCode]; - errorCode.ToString().Should().Be(expectedErrorCode); + Assert.Equal(expectedErrorCode, errorCode.ToString()); } @@ -159,22 +158,22 @@ public async Task WhenClassRefIsChanged_ShouldUpdateApplicationMetadata(string o initModelName = Path.GetFileNameWithoutExtension(initModelName); var newJsonSchemaResponse = await GenerateNewJsonSchema(initModelName, $"{VersionPrefix(org, targetRepository)}/new"); - newJsonSchemaResponse.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, newJsonSchemaResponse.StatusCode); var generateInitModelsResponse = await GenerateModels(initModelName, $"{VersionPrefix(org, targetRepository)}/datamodel?modelPath={modelPath}"); - generateInitModelsResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, generateInitModelsResponse.StatusCode); // Check classRef in application metadata string applicationMetadataContent = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); var applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataContent, JsonSerializerOptions); - applicationMetadata.DataTypes.Single(d => d.Id == initModelName).AppLogic.ClassRef.Should().Be($"Altinn.App.Models.{initModelName}.{initModelName}"); + Assert.Equal(applicationMetadata.DataTypes.Single(d => d.Id == initModelName).AppLogic.ClassRef, $"Altinn.App.Models.{initModelName}.{initModelName}"); var generateNewModelsResponse = await GenerateModels(newModelName, $"{VersionPrefix(org, targetRepository)}/datamodel?modelPath={modelPath}"); - generateNewModelsResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, generateNewModelsResponse.StatusCode); // Check classRef in application metadata applicationMetadataContent = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataContent, JsonSerializerOptions); - applicationMetadata.DataTypes.Single(d => d.Id == initModelName).AppLogic.ClassRef.Should().Be($"Altinn.App.Models.{newModelName}.{newModelName}"); + Assert.Equal(applicationMetadata.DataTypes.Single(d => d.Id == initModelName).AppLogic.ClassRef, $"Altinn.App.Models.{newModelName}.{newModelName}"); } private async Task<HttpResponseMessage> UploadNewXsdSchema(string xsdPath, string url) diff --git a/backend/tests/Designer.Tests/Controllers/DataModelsController/DeleteDatamodelTests.cs b/backend/tests/Designer.Tests/Controllers/DataModelsController/DeleteDatamodelTests.cs index 315e6aa82f9..4610f88cd28 100644 --- a/backend/tests/Designer.Tests/Controllers/DataModelsController/DeleteDatamodelTests.cs +++ b/backend/tests/Designer.Tests/Controllers/DataModelsController/DeleteDatamodelTests.cs @@ -8,7 +8,6 @@ using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Mocks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -43,7 +42,7 @@ public async Task Delete_Datamodel_FromDataModelRepo_Ok(string org, string repo, using HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, dataPathWithData); using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } [Theory] @@ -55,18 +54,18 @@ public async Task Delete_Datamodel_FromAppWhereDataModelHasMultipleLayoutSetConn string dataPathWithData = $"{VersionPrefix(org, targetRepository)}/datamodel?modelPath={modelPath}"; string applicationMetadataFromRepoBefore = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); string layoutSetsFromRepoBefore = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/ui/layout-sets.json"); - applicationMetadataFromRepoBefore.Should().Contain("datamodel"); + Assert.Contains("datamodel", applicationMetadataFromRepoBefore); LayoutSets layoutSets = JsonSerializer.Deserialize<LayoutSets>(layoutSetsFromRepoBefore, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - layoutSets.Sets.FindAll(set => set.DataType == "datamodel").Count.Should().Be(2); + Assert.Equal(2, layoutSets.Sets.FindAll(set => set.DataType == "datamodel").Count); using HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Delete, dataPathWithData); using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); string applicationMetadataFromRepoAfter = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); string layoutSetsFromRepoAfter = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/ui/layout-sets.json"); - applicationMetadataFromRepoAfter.Should().NotContain("datamodel"); - layoutSetsFromRepoAfter.Should().NotContain("datamodel"); + Assert.DoesNotContain("datamodel", applicationMetadataFromRepoAfter); + Assert.DoesNotContain("datamodel", layoutSetsFromRepoAfter); } } diff --git a/backend/tests/Designer.Tests/Controllers/DataModelsController/GetTests.cs b/backend/tests/Designer.Tests/Controllers/DataModelsController/GetTests.cs index 31c013aa15f..6859a620a3f 100644 --- a/backend/tests/Designer.Tests/Controllers/DataModelsController/GetTests.cs +++ b/backend/tests/Designer.Tests/Controllers/DataModelsController/GetTests.cs @@ -2,11 +2,12 @@ using System.Net; using System.Net.Http; using System.Net.Http.Json; +using System.Text.Json; using System.Threading.Tasks; using Altinn.Platform.Storage.Interface.Models; using Designer.Tests.Controllers.ApiTests; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; +using SharedResources.Tests; using Xunit; namespace Designer.Tests.Controllers.DataModelsController; @@ -27,7 +28,7 @@ public async Task GetDatamodel_ValidPath_ShouldReturnContent(string modelPath, s using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Theory] @@ -38,22 +39,24 @@ public async Task GetDatamodelDataType_ShouldReturnContent(string modelName, str using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); DataType dataTypeResponse = await response.Content.ReadFromJsonAsync<DataType>(); - dataTypeResponse.Should().NotBeNull(); - dataTypeResponse.Should().BeEquivalentTo(new DataType - { - Id = "Kursdomene_HvemErHvem_M_2021-04-08_5742_34627_SERES", - AllowedContentTypes = new List<string> { "application/xml" }, - AppLogic = new ApplicationLogic + Assert.NotNull(dataTypeResponse); + Assert.True(JsonUtils.DeepEquals(JsonSerializer.Serialize(dataTypeResponse, JsonSerializerOptions), + JsonSerializer.Serialize(new DataType { - AutoCreate = true, - ClassRef = "Altinn.App.Models.HvemErHvem_M" - }, - TaskId = "Task_1", - MaxCount = 1, - MinCount = 1 - }); + Id = "Kursdomene_HvemErHvem_M_2021-04-08_5742_34627_SERES", + AllowedContentTypes = new List<string> { "application/xml" }, + AppLogic = new ApplicationLogic + { + AutoCreate = true, + ClassRef = "Altinn.App.Models.HvemErHvem_M" + }, + TaskId = "Task_1", + MaxCount = 1, + MinCount = 1 + }, JsonSerializerOptions) + )); } [Theory] @@ -64,7 +67,7 @@ public async Task GetDatamodelDataType_ShouldNullWhenNotFound(string modelName, using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); - (await response.Content.ReadAsStringAsync()).Should().Be("null"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("null", await response.Content.ReadAsStringAsync()); } } diff --git a/backend/tests/Designer.Tests/Controllers/DataModelsController/GetXsdDatamodelsTests.cs b/backend/tests/Designer.Tests/Controllers/DataModelsController/GetXsdDatamodelsTests.cs index 6087f2b25cf..7e0059f01cf 100644 --- a/backend/tests/Designer.Tests/Controllers/DataModelsController/GetXsdDatamodelsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/DataModelsController/GetXsdDatamodelsTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Altinn.Studio.Designer.Models; using Designer.Tests.Controllers.ApiTests; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -28,7 +27,7 @@ public async Task GetXsdDatamodels_NoInput_ShouldReturnAllModels(string org, str var response = await HttpClient.SendAsync(httpRequestMessage); var altinnCoreFiles = await response.Content.ReadAsAsync<List<AltinnCoreFile>>(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - altinnCoreFiles.Count.Should().Be(2); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(2, altinnCoreFiles.Count); } } diff --git a/backend/tests/Designer.Tests/Controllers/DataModelsController/PutDatamodelTests.cs b/backend/tests/Designer.Tests/Controllers/DataModelsController/PutDatamodelTests.cs index 4db5c7cf72f..dc89a535820 100644 --- a/backend/tests/Designer.Tests/Controllers/DataModelsController/PutDatamodelTests.cs +++ b/backend/tests/Designer.Tests/Controllers/DataModelsController/PutDatamodelTests.cs @@ -12,6 +12,7 @@ using System.Web; using System.Xml; using System.Xml.Schema; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Platform.Storage.Interface.Models; using Altinn.Studio.DataModeling.Converter.Json; using Altinn.Studio.DataModeling.Converter.Json.Strategy; @@ -21,7 +22,6 @@ using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Controllers.DataModelsController.Utils; using Designer.Tests.Utils; -using FluentAssertions; using Json.Pointer; using Json.Schema; using Microsoft.AspNetCore.Mvc; @@ -64,7 +64,7 @@ public async Task ValidInput_ShouldReturn_NoContent_And_Create_Files(string mode }; var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); await FilesWithCorrectNameAndContentShouldBeCreated(modelName); } @@ -82,19 +82,19 @@ public async Task InvalidInput_ShouldReturn_BadRequest_And_CustomErrorMessages(s }; var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); var problemDetailsJson = await response.Content.ReadAsStringAsync(); var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(problemDetailsJson); - problemDetails.Should().NotBeNull(); - problemDetails.Extensions.Should().ContainKey("customErrorMessages"); + Assert.NotNull(problemDetails); + Assert.Contains("customErrorMessages", problemDetails.Extensions.Keys); var customErrorMessages = problemDetails.Extensions["customErrorMessages"]; - customErrorMessages.Should().NotBeNull(); + Assert.NotNull(customErrorMessages); var customErrorMessagesElement = (JsonElement)customErrorMessages; var firstErrorMessage = customErrorMessagesElement.EnumerateArray().FirstOrDefault().GetString(); - firstErrorMessage.Should().Contain("'root': member names cannot be the same as their enclosing type"); + Assert.Contains("'root': member names cannot be the same as their enclosing type", firstErrorMessage); } [Theory] @@ -112,7 +112,7 @@ public async Task ValidSchema_ShouldReturn_NoContent_And_Create_Files(string mod }; var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); } [Theory(Skip = "Validator is excluded from put method for now.")] @@ -130,7 +130,7 @@ public async Task IncompatibleSchema_ShouldReturn422(string modelPath, string sc }; var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode); string content = await response.Content.ReadAsStringAsync(); var errorResponse = JsonSerializer.Deserialize<ValidationProblemDetails>(content, new JsonSerializerOptions() @@ -142,7 +142,7 @@ public async Task IncompatibleSchema_ShouldReturn422(string modelPath, string sc { var pointerObject = JsonPointer.Parse(pointer); Assert.Single(errorResponse.Errors.Keys, p => JsonPointer.Parse(p) == pointerObject); - errorResponse.Errors[pointerObject.ToString(JsonPointerStyle.UriEncoded)].Contains(errorCode).Should().BeTrue(); + Assert.Contains(errorCode, errorResponse.Errors[pointerObject.ToString(JsonPointerStyle.UriEncoded)]); } } @@ -165,10 +165,10 @@ public async Task PutDatamodelDataType_ShouldReturnWithoutErrors(string datamode }; HttpResponseMessage response = await HttpClient.SendAsync(putRequest); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); DataType dataTypeResponse = await response.Content.ReadFromJsonAsync<DataType>(); - dataTypeResponse.Should().NotBeNull(); - dataTypeResponse.Should().BeEquivalentTo(dataType); + Assert.NotNull(dataTypeResponse); + AssertionUtil.AssertEqualTo(dataType, dataTypeResponse); } [Theory] @@ -190,7 +190,7 @@ public async Task PutDatamodelDataType_FailsIfDatamodelNameMismatchesObjectId(st }; HttpResponseMessage response = await HttpClient.SendAsync(putRequest); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } @@ -235,7 +235,7 @@ private static void VerifyMetadataContent(string path) private static void VerifyFileContent(string path, string expectedContent) { var fileContent = File.ReadAllText(path); - expectedContent.Should().Be(fileContent); + Assert.Equal(expectedContent, fileContent); } public static IEnumerable<object[]> IncompatibleSchemasTestData => new List<object[]> diff --git a/backend/tests/Designer.Tests/Controllers/DataModelsController/UseXsdFromRepoTests.cs b/backend/tests/Designer.Tests/Controllers/DataModelsController/UseXsdFromRepoTests.cs index da7b70c4fb0..77f548672f8 100644 --- a/backend/tests/Designer.Tests/Controllers/DataModelsController/UseXsdFromRepoTests.cs +++ b/backend/tests/Designer.Tests/Controllers/DataModelsController/UseXsdFromRepoTests.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -29,6 +28,6 @@ public async Task UseXsdFromRepo_DatamodelsRepo_ShouldReturnCreated(string org, using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); } } diff --git a/backend/tests/Designer.Tests/Controllers/DataModelsController/Utils/FileContentVerifier.cs b/backend/tests/Designer.Tests/Controllers/DataModelsController/Utils/FileContentVerifier.cs index f3286f4c14b..8d7541e952e 100644 --- a/backend/tests/Designer.Tests/Controllers/DataModelsController/Utils/FileContentVerifier.cs +++ b/backend/tests/Designer.Tests/Controllers/DataModelsController/Utils/FileContentVerifier.cs @@ -1,6 +1,6 @@ using System.IO; -using FluentAssertions; using SharedResources.Tests; +using Xunit; namespace Designer.Tests.Controllers.DataModelsController.Utils { @@ -10,7 +10,7 @@ public static class FileContentVerifier public static void VerifyJsonFileContent(string path, string json) { string fileContent = File.ReadAllText(path); - JsonUtils.DeepEquals(fileContent, json).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(fileContent, json)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/FeedbackFormController/SendMessageToSlackTests.cs b/backend/tests/Designer.Tests/Controllers/FeedbackFormController/SendMessageToSlackTests.cs index 5488681f842..bb257b7c543 100644 --- a/backend/tests/Designer.Tests/Controllers/FeedbackFormController/SendMessageToSlackTests.cs +++ b/backend/tests/Designer.Tests/Controllers/FeedbackFormController/SendMessageToSlackTests.cs @@ -8,7 +8,6 @@ using Designer.Tests.Controllers.FeedbackFormController.Base; using Designer.Tests.Controllers.FeedbackFormController.Utils; using Designer.Tests.Fixtures; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -55,7 +54,7 @@ public async Task SendMessageToSlack_Should_ReturnOk() using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Fact] @@ -68,7 +67,7 @@ public async Task SendMessageToSlack_NullAnswers_Should_ReturnBadRequest() }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Fact] @@ -84,6 +83,6 @@ public async Task SendMessageToSlack_WithMissingAnswers_Should_ReturnBadRequest( }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } } diff --git a/backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionListsReferencesTests.cs b/backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionListsReferencesTests.cs index d4fdd5d11db..da8f3556b6b 100644 --- a/backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionListsReferencesTests.cs +++ b/backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionListsReferencesTests.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; +using System.IO; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; +using Designer.Tests.Utils; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -14,6 +16,7 @@ public class GetOptionListsReferencesTests : DesignerEndpointsTestsBase<GetOptio { const string RepoWithUsedOptions = "app-with-options"; const string RepoWithUnusedOptions = "app-with-layoutsets"; + const string RepoWithoutOptions = "empty-app"; public GetOptionListsReferencesTests(WebApplicationFactory<Program> factory) : base(factory) { @@ -85,4 +88,25 @@ public async Task GetOptionListsReferences_Returns200Ok_WithEmptyOptionsReferenc Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode); Assert.Equivalent("[]", responseBody); } + + [Fact] + public async Task GetOptionListsReferences_Returns200Ok_WithEmptyOptionsReferences_WhenAppDoesNotHaveOptions() + { + string targetRepository = TestDataHelper.GenerateTestRepoName(); + await CopyRepositoryForTest("ttd", RepoWithoutOptions, "testUser", targetRepository); + string repoPath = TestDataHelper.GetTestDataRepositoryDirectory("ttd", targetRepository, "testUser"); + string exampleLayout = @"{ ""data"": {""layout"": []}}"; + string filePath = Path.Combine(repoPath, "App/ui/form/layouts"); + Directory.CreateDirectory(filePath); + await File.WriteAllTextAsync(Path.Combine(filePath, "exampleLayout.json"), exampleLayout); + + string apiUrl = $"/designer/api/ttd/{targetRepository}/options/usage"; + using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, apiUrl); + + using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); + string responseBody = await response.Content.ReadAsStringAsync(); + + Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode); + Assert.Equivalent("[]", responseBody); + } } diff --git a/backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionsTests.cs b/backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionsTests.cs index f0ae8b42f1b..8f9b391c321 100644 --- a/backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionsTests.cs @@ -1,15 +1,16 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Filters; using Altinn.Studio.Designer.Models; using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; @@ -91,18 +92,18 @@ public async Task GetOptionLists_Returns200OK_WithOptionListsData() // Assert Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode); - responseList.Should().BeEquivalentTo(new List<OptionListData> - { - new () { Title = "options-with-null-fields", Data = null, HasError = true }, - new () { Title = "other-options", HasError = false }, - new () { Title = "test-options", HasError = false }, - new () { Title = "optionListMissingValue", Data = null, HasError = true }, - new () { Title = "optionListMissingLabel", Data = null, HasError = true }, - new () { Title = "optionListTrailingComma", Data = null, HasError = true }, - new () { Title = "optionListLabelWithObject", Data = null, HasError = true }, - new () { Title = "optionListLabelWithNumber", Data = null, HasError = true }, - new () { Title = "optionListLabelWithBool", Data = null, HasError = true } - }, options => options.Excluding(x => x.Data)); + + Assert.Equal(9, responseList.Count); + Assert.Single(responseList, o => o.Title == "options-with-null-fields" && o.HasError == true); + Assert.Single(responseList, o => o.Title == "other-options" && o.HasError == false); + Assert.Single(responseList, o => o.Title == "test-options" && o.HasError == false); + Assert.Single(responseList, o => o.Title == "optionListMissingValue" && o.HasError == true); + Assert.Single(responseList, o => o.Title == "optionListMissingLabel" && o.HasError == true); + Assert.Single(responseList, o => o.Title == "optionListTrailingComma" && o.HasError == true); + Assert.Single(responseList, o => o.Title == "optionListLabelWithObject" && o.HasError == true); + Assert.Single(responseList, o => o.Title == "optionListLabelWithNumber" && o.HasError == true); + Assert.Single(responseList, o => o.Title == "optionListLabelWithBool" && o.HasError == true); + } [Fact] @@ -165,8 +166,8 @@ public async Task GetSingleOptionsList_Returns400BadRequest_WhenOptionsListIsInv Assert.Equal(StatusCodes.Status400BadRequest, (int)response.StatusCode); var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(await response.Content.ReadAsStringAsync()); - problemDetails.Should().NotBeNull(); + Assert.NotNull(problemDetails); JsonElement errorCode = (JsonElement)problemDetails.Extensions[ProblemDetailsExtensionsCodes.ErrorCode]; - errorCode.ToString().Should().Be("InvalidOptionsFormat"); + Assert.Equal("InvalidOptionsFormat", errorCode.ToString()); } } diff --git a/backend/tests/Designer.Tests/Controllers/OptionsController/UpdateOptionsTests.cs b/backend/tests/Designer.Tests/Controllers/OptionsController/UpdateOptionsTests.cs index 0b6fa37c012..0c91be21af3 100644 --- a/backend/tests/Designer.Tests/Controllers/OptionsController/UpdateOptionsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/OptionsController/UpdateOptionsTests.cs @@ -7,7 +7,6 @@ using Altinn.Studio.Designer.Models; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; @@ -200,9 +199,9 @@ public async Task Put_Returns_400BadRequest_When_Option_Value_Is_Invalid() // Assert Assert.Equal(StatusCodes.Status400BadRequest, (int)response.StatusCode); - problemDetails.Should().NotBeNull(); + Assert.NotNull(problemDetails); JsonElement errorCode = (JsonElement)problemDetails.Extensions[ProblemDetailsExtensionsCodes.ErrorCode]; - errorCode.ToString().Should().Be("InvalidOptionsFormat"); + Assert.Equal("InvalidOptionsFormat", errorCode.ToString()); } [Fact] diff --git a/backend/tests/Designer.Tests/Controllers/OptionsController/UploadOptionsTests.cs b/backend/tests/Designer.Tests/Controllers/OptionsController/UploadOptionsTests.cs index 709ed0befde..ca9f752fad8 100644 --- a/backend/tests/Designer.Tests/Controllers/OptionsController/UploadOptionsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/OptionsController/UploadOptionsTests.cs @@ -8,7 +8,6 @@ using Altinn.Studio.Designer.Models; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; @@ -105,7 +104,7 @@ public async Task Post_Returns_400BadRequest_When_Uploading_New_OptionsList_With string optionsFileName = "missing-fields-options.json"; string jsonOptions = @"[ - {""value"": """" }, + {""value"": """" }, {""label"": """" }, ]"; @@ -189,8 +188,8 @@ public async Task Post_Returns_400BadRequest_When_Uploading_New_OptionsList_With Assert.Equal(StatusCodes.Status400BadRequest, (int)response.StatusCode); var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(await response.Content.ReadAsStringAsync()); - problemDetails.Should().NotBeNull(); + Assert.NotNull(problemDetails); JsonElement errorCode = (JsonElement)problemDetails.Extensions[ProblemDetailsExtensionsCodes.ErrorCode]; - errorCode.ToString().Should().Be("InvalidOptionsFormat"); + Assert.Equal("InvalidOptionsFormat", errorCode.ToString()); } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/AnonymousTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/AnonymousTests.cs index f3accf4071a..95c968a82bb 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/AnonymousTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/AnonymousTests.cs @@ -2,7 +2,6 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -26,7 +25,7 @@ public async Task Get_Anonymous_Ok() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals("{}", responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals("{}", responseBody)); } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/ApplicationMetadataTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/ApplicationMetadataTests.cs index c8ecc70b75d..2e3dc56876f 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/ApplicationMetadataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/ApplicationMetadataTests.cs @@ -10,7 +10,6 @@ using Altinn.Studio.Designer.Services.Interfaces; using Designer.Tests.Mocks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -54,7 +53,7 @@ public async Task Get_ApplicationMetadata_Ok() ApplicationMetadata expectedApplicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(expectedApplicationMetadataString, JsonSerializerOptions); expectedApplicationMetadata.AltinnNugetVersion = string.Empty; string expectedJson = JsonSerializer.Serialize(expectedApplicationMetadata, JsonSerializerOptions); - JsonUtils.DeepEquals(expectedJson, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedJson, responseBody)); } [Fact] @@ -75,7 +74,7 @@ public async Task Get_ApplicationMetadata_With_V8_Altinn_Nuget_Version_Ok() ApplicationMetadata expectedApplicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(expectedApplicationMetadataString, JsonSerializerOptions); expectedApplicationMetadata.AltinnNugetVersion = "8.0.0.0"; string expectedJson = JsonSerializer.Serialize(expectedApplicationMetadata, JsonSerializerOptions); - JsonUtils.DeepEquals(expectedJson, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedJson, responseBody)); } [Fact] @@ -83,10 +82,11 @@ public async Task Get_ApplicationMetadata_WithAllPartyTypesAllowedSetToFalse() { string originalApplicationMetadataString = TestDataHelper.GetFileFromRepo(Org, AppV4, Developer, "App/config/applicationmetadata.json"); ApplicationMetadata originalApplicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(originalApplicationMetadataString, JsonSerializerOptions); - originalApplicationMetadata.PartyTypesAllowed.Person.Should().BeTrue(); - originalApplicationMetadata.PartyTypesAllowed.Organisation.Should().BeTrue(); - originalApplicationMetadata.PartyTypesAllowed.SubUnit.Should().BeTrue(); - originalApplicationMetadata.PartyTypesAllowed.BankruptcyEstate.Should().BeTrue(); + + Assert.True(originalApplicationMetadata.PartyTypesAllowed.Person); + Assert.True(originalApplicationMetadata.PartyTypesAllowed.Organisation); + Assert.True(originalApplicationMetadata.PartyTypesAllowed.SubUnit); + Assert.True(originalApplicationMetadata.PartyTypesAllowed.BankruptcyEstate); _appDevelopmentServiceMock .Setup(rs => rs.GetAppLibVersion(It.IsAny<AltinnRepoEditingContext>())) @@ -102,10 +102,11 @@ public async Task Get_ApplicationMetadata_WithAllPartyTypesAllowedSetToFalse() string responseBody = await response.Content.ReadAsStringAsync(); ApplicationMetadata responseApplicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(responseBody, JsonSerializerOptions); - responseApplicationMetadata.PartyTypesAllowed.Person.Should().BeFalse(); - responseApplicationMetadata.PartyTypesAllowed.Organisation.Should().BeFalse(); - responseApplicationMetadata.PartyTypesAllowed.SubUnit.Should().BeFalse(); - responseApplicationMetadata.PartyTypesAllowed.BankruptcyEstate.Should().BeFalse(); + + Assert.False(responseApplicationMetadata.PartyTypesAllowed.Person); + Assert.False(responseApplicationMetadata.PartyTypesAllowed.Organisation); + Assert.False(responseApplicationMetadata.PartyTypesAllowed.SubUnit); + Assert.False(responseApplicationMetadata.PartyTypesAllowed.BankruptcyEstate); } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/DatamodelTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/DatamodelTests.cs index 51620b90ff5..f78065f829e 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/DatamodelTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/DatamodelTests.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Altinn.Studio.Designer.Services.Implementation; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -29,7 +28,7 @@ public async Task Get_Datamodel_Ok() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedDatamodel, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedDatamodel, responseBody)); } [Fact] @@ -45,7 +44,7 @@ public async Task Get_Datamodel_MockedDataTypeId_OkWithDefaultDataModel() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedDatamodel, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedDatamodel, responseBody)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/GetFooterTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/GetFooterTests.cs index 23d979706af..91e6ce3ef1a 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/GetFooterTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/GetFooterTests.cs @@ -4,11 +4,10 @@ using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Models; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; -using SharedResources.Tests; using Xunit; namespace Designer.Tests.Controllers.PreviewController @@ -34,7 +33,7 @@ public async Task Get_Footer_Exists_Ok() string responseString = await response.Content.ReadAsStringAsync(); FooterFile responseFooterFile = JsonSerializer.Deserialize<FooterFile>(responseString); - responseFooterFile.Footer.Should().BeEquivalentTo(actualFooterFile.Footer); + AssertionUtil.AssertEqualTo(actualFooterFile.Footer, responseFooterFile.Footer); } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormLayoutsTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormLayoutsTests.cs index d5644f87e52..ca256de8287 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormLayoutsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormLayoutsTests.cs @@ -1,7 +1,6 @@ using System.Net.Http; using System.Threading.Tasks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; @@ -29,7 +28,7 @@ public async Task Get_FormLayouts_Ok() Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedFormLayouts, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedFormLayouts, responseBody)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormLayoutsV4Tests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormLayoutsV4Tests.cs index 770c9fa0e6d..c3e388c1fb6 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormLayoutsV4Tests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormLayoutsV4Tests.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Threading.Tasks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -30,7 +29,7 @@ public async Task Get_FormLayoutsForV4App_Ok() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedFormLayouts, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedFormLayouts, responseBody)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/GetRuleConfigurationTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/GetRuleConfigurationTests.cs index 71ce47d70c7..0b893ae49b4 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/GetRuleConfigurationTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/GetRuleConfigurationTests.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Threading.Tasks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -29,7 +28,7 @@ public async Task Get_RuleConfiguration_Ok() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedRuleConfig, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedRuleConfig, responseBody)); } [Fact] diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/GetRuleConfigurationV4Tests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/GetRuleConfigurationV4Tests.cs index 55a419e1be2..1a2a6d1ce8d 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/GetRuleConfigurationV4Tests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/GetRuleConfigurationV4Tests.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Threading.Tasks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -38,7 +37,7 @@ public async Task Get_RuleConfigurationForV4AppWithRuleConfig_Ok() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedRuleConfig, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedRuleConfig, responseBody)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/LayoutSettingsTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/LayoutSettingsTests.cs index 16f328fbc44..d010b95fe40 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/LayoutSettingsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/LayoutSettingsTests.cs @@ -3,7 +3,6 @@ using System.Net.Http; using System.Threading.Tasks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -27,7 +26,7 @@ public async Task Get_LayoutSettings_Ok() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedLayoutSettings, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedLayoutSettings, responseBody)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/LayoutSettingsV4Tests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/LayoutSettingsV4Tests.cs index 3967b59a937..2de11e45218 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/LayoutSettingsV4Tests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/LayoutSettingsV4Tests.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Threading.Tasks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -27,7 +26,7 @@ public async Task Get_LayoutSettingsForV4Apps_Ok() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedLayoutSettings, responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedLayoutSettings, responseBody)); } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/ValidateInstantiationTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/ValidateInstantiationTests.cs index 90ceb284e79..810fa912d0a 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/ValidateInstantiationTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/ValidateInstantiationTests.cs @@ -1,7 +1,6 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -25,7 +24,7 @@ public async Task Post_ValidateInstantiation_Ok() Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(@"{""valid"": true}", responseBody).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(@"{""valid"": true}", responseBody)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/AddDataTypeToApplicationMetadataTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/AddDataTypeToApplicationMetadataTests.cs index ed4965e9587..faf7de75baa 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/AddDataTypeToApplicationMetadataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/AddDataTypeToApplicationMetadataTests.cs @@ -3,10 +3,10 @@ using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Platform.Storage.Interface.Models; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -30,7 +30,7 @@ public async Task AddDataTypeToApplicationMetadata_ShouldAddDataTypeAndReturnOK( using var request = new HttpRequestMessage(HttpMethod.Post, url); using var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string appMetadataString = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); Application appMetadata = JsonSerializer.Deserialize<Application>(appMetadataString, new JsonSerializerOptions @@ -51,9 +51,9 @@ public async Task AddDataTypeToApplicationMetadata_ShouldAddDataTypeAndReturnOK( EnabledFileValidators = new List<string>() }; - appMetadata.DataTypes.Count.Should().Be(2); - appMetadata.DataTypes.Find(dataType => dataType.Id == dataTypeId).Should().BeEquivalentTo(expectedDataType); - appMetadata.DataTypes.Find(dataType => dataType.Id == dataTypeId).TaskId.Should().Be(taskId); + Assert.Equal(2, appMetadata.DataTypes.Count); + AssertionUtil.AssertEqualTo(expectedDataType, appMetadata.DataTypes.Find(dataType => dataType.Id == dataTypeId)); + Assert.Equal(taskId, appMetadata.DataTypes.Find(dataType => dataType.Id == dataTypeId).TaskId); } [Theory] @@ -66,7 +66,7 @@ public async Task AddDataTypeToApplicationMetadataWhenExists_ShouldNotAddDataTyp using var request = new HttpRequestMessage(HttpMethod.Post, url); using var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string appMetadataString = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); Application appMetadata = JsonSerializer.Deserialize<Application>(appMetadataString, new JsonSerializerOptions @@ -74,7 +74,7 @@ public async Task AddDataTypeToApplicationMetadataWhenExists_ShouldNotAddDataTyp PropertyNameCaseInsensitive = true }); - appMetadata.DataTypes.Count.Should().Be(1); + Assert.Single(appMetadata.DataTypes); } } } diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/DeleteDataTypeFromApplicationMetadataTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/DeleteDataTypeFromApplicationMetadataTests.cs index 2d6e6c141b6..2877f2eb576 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/DeleteDataTypeFromApplicationMetadataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/DeleteDataTypeFromApplicationMetadataTests.cs @@ -5,7 +5,6 @@ using Altinn.Platform.Storage.Interface.Models; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -29,7 +28,7 @@ public async Task DeleteDataTypeFromApplicationMetadata_ShouldDeleteDataTypeAndR using var request = new HttpRequestMessage(HttpMethod.Delete, url); using var response = await HttpClient.SendAsync(request); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string appMetadataString = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); Application appMetadata = JsonSerializer.Deserialize<Application>(appMetadataString, new JsonSerializerOptions @@ -37,7 +36,7 @@ public async Task DeleteDataTypeFromApplicationMetadata_ShouldDeleteDataTypeAndR PropertyNameCaseInsensitive = true }); - appMetadata.DataTypes.Count.Should().Be(0); + Assert.Empty(appMetadata.DataTypes); } } } diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/DataTypesChangeTests/ApplicationMetadataFileSyncDataTypesTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/DataTypesChangeTests/ApplicationMetadataFileSyncDataTypesTests.cs index 57d5ebf0b40..e2867449214 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/DataTypesChangeTests/ApplicationMetadataFileSyncDataTypesTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/DataTypesChangeTests/ApplicationMetadataFileSyncDataTypesTests.cs @@ -9,7 +9,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -42,13 +41,13 @@ public async Task ProcessDataTypesChangedNotify_TaskIsDisConnectedFromDataType_S Content = new StringContent(dataTypeChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string applicationMetadataFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); ApplicationMetadata applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataFromRepo, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - applicationMetadata.DataTypes.Should().NotContain(dataType => dataType.AppLogic != null && dataType.TaskId == dataTypesChange.ConnectedTaskId); // No data type connected to Task_1 + Assert.DoesNotContain(applicationMetadata.DataTypes, dataType => dataType.AppLogic != null && dataType.TaskId == dataTypesChange.ConnectedTaskId); // Task_1 is not connected to any data type } [Theory] @@ -73,13 +72,14 @@ public async Task ProcessDataTypesChangedNotify_NewDataTypeForTask5IsMessage_Sho Content = new StringContent(dataTypesChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string applicationMetadataFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); ApplicationMetadata applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataFromRepo, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - applicationMetadata.DataTypes.Find(type => type.Id == dataTypeToConnect).TaskId.Should().Be(task); // Data type 'message' is now connected to Task_5 + Assert.Equal(task, applicationMetadata.DataTypes.Find(type => type.Id == dataTypeToConnect).TaskId); // Data type 'message' is now connected to Task_5; + } [Theory] @@ -105,14 +105,15 @@ public async Task ProcessDataTypesChangedNotify_NewDataTypesForTask5IsMessageAnd Content = new StringContent(dataTypesChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string applicationMetadataFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); ApplicationMetadata applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataFromRepo, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - applicationMetadata.DataTypes.FindAll(type => type.TaskId == task).Count.Should().Be(2); // Original connected data type 'datalist' should be disconnected - applicationMetadata.DataTypes.Find(type => type.Id == dataTypeToConnect1).TaskId.Should().Be(task); // Data type 'message' is now connected to Task_5 - applicationMetadata.DataTypes.Find(type => type.Id == dataTypeToConnect2).TaskId.Should().Be(task); // Data type 'likert' is now connected to Task_5 + + Assert.Equal(2, applicationMetadata.DataTypes.FindAll(type => type.TaskId == task).Count); // Original connected data type 'datalist' should be disconnected + Assert.Equal(task, applicationMetadata.DataTypes.Find(type => type.Id == dataTypeToConnect1).TaskId); // Data type 'message' is now connected to Task_5 + Assert.Equal(task, applicationMetadata.DataTypes.Find(type => type.Id == dataTypeToConnect2).TaskId); // Data type 'likert' is now connected to Task_5 } [Theory] @@ -137,12 +138,12 @@ public async Task ProcessDataTypeChangedNotify_NewDataTypeForCustomReceipt_Shoul Content = new StringContent(dataTypesChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string applicationMetadataFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); ApplicationMetadata applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataFromRepo, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - applicationMetadata.DataTypes.Find(type => type.Id == dataTypeToConnect).TaskId.Should().NotBe(task); // CustomReceipt has not been added to the dataType + Assert.DoesNotContain(applicationMetadata.DataTypes, dataType => dataType.AppLogic != null && dataType.TaskId == dataTypesChange.ConnectedTaskId); // No data type connected to Task_1 } public static IEnumerable<object[]> ProcessDataTypeChangedNotifyTestData() diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/DataTypesChangeTests/LayoutSetsFileSyncDataTypesTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/DataTypesChangeTests/LayoutSetsFileSyncDataTypesTests.cs index a369726e20c..a6ea1aacee9 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/DataTypesChangeTests/LayoutSetsFileSyncDataTypesTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/DataTypesChangeTests/LayoutSetsFileSyncDataTypesTests.cs @@ -9,7 +9,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -44,14 +43,14 @@ public async Task ProcessDataTypesChangedNotify_Task1DisconnectedFromDataType_Sh Content = new StringContent(dataTypesChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string layoutSetsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/ui/layout-sets.json"); LayoutSets layoutSets = JsonSerializer.Deserialize<LayoutSets>(layoutSetsFromRepo, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - layoutSets.Sets.Find(set => set.Tasks[0] == dataTypesChange.ConnectedTaskId).DataType.Should().BeNull(); + Assert.Null(layoutSets.Sets.Find(set => set.Tasks[0] == dataTypesChange.ConnectedTaskId).DataType); } [Theory] @@ -76,14 +75,14 @@ public async Task ProcessDataTypesChangedNotify_NewDataTypeForTask5IsMessage_Sho Content = new StringContent(dataTypesChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string layoutSetsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/ui/layout-sets.json"); LayoutSets layoutSets = JsonSerializer.Deserialize<LayoutSets>(layoutSetsFromRepo, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - layoutSets.Sets.Find(set => set.Tasks[0] == task).DataType.Should().Be(dataTypeToConnect); + Assert.Equal(dataTypeToConnect, layoutSets.Sets.Find(set => set.Tasks[0] == task).DataType); } [Theory] @@ -108,14 +107,14 @@ public async Task ProcessDataTypesChangedNotify_NewDataTypesForTask5IsMessageAnd Content = new StringContent(dataTypesChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string layoutSetsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/ui/layout-sets.json"); LayoutSets layoutSets = JsonSerializer.Deserialize<LayoutSets>(layoutSetsFromRepo, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - layoutSets.Sets.Find(set => set.Tasks[0] == task).DataType.Should().Be(dataTypeToConnect1); + Assert.Equal(dataTypeToConnect1, layoutSets.Sets.Find(set => set.Tasks[0] == task).DataType); } [Theory] @@ -140,14 +139,14 @@ public async Task ProcessDataTypesChangedNotify_NewDataTypesForTask5IsMessageAnd Content = new StringContent(dataTypesChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string layoutSetsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/ui/layout-sets.json"); LayoutSets layoutSets = JsonSerializer.Deserialize<LayoutSets>(layoutSetsFromRepo, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - layoutSets.Sets.Find(set => set.Tasks[0] == task).DataType.Should().Be(dataTypeToConnect2); + Assert.Equal(dataTypeToConnect2, layoutSets.Sets.Find(set => set.Tasks[0] == task).DataType); } [Theory] @@ -171,14 +170,14 @@ public async Task ProcessDataTypeChangedNotify_NewDataTypeForCustomReceipt_Shoul Content = new StringContent(dataTypeChangeString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string layoutSetsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/ui/layout-sets.json"); LayoutSets layoutSets = JsonSerializer.Deserialize<LayoutSets>(layoutSetsFromRepo, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - layoutSets.Sets.Find(set => set.Tasks[0] == task).DataType.Should().Be(dataTypeToConnect); + Assert.Equal(dataTypeToConnect, layoutSets.Sets.Find(set => set.Tasks[0] == task).DataType); } private async Task AddFileToRepo(string fileToCopyPath, string relativeCopyRepoLocation) diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/ApplicationMetadataFileSyncTaskIdTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/ApplicationMetadataFileSyncTaskIdTests.cs index 52b5dcb9816..ff13c6befc8 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/ApplicationMetadataFileSyncTaskIdTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/ApplicationMetadataFileSyncTaskIdTests.cs @@ -11,7 +11,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -49,14 +48,14 @@ public async Task UpsertProcessDefinition_ShouldSyncApplicationMetadata(string o form.Add(new StringContent(metadataString, Encoding.UTF8, MediaTypeNames.Application.Json), "metadata"); using var response = await HttpClient.PutAsync(url, form); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string applicationMetadataFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/applicationmetadata.json"); ApplicationMetadata applicationMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(applicationMetadataFromRepo, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - applicationMetadata.DataTypes.Should().NotContain(dataType => dataType.TaskId == metadata.TaskIdChange.OldId); - applicationMetadata.DataTypes.Should().Contain(dataType => dataType.TaskId == metadata.TaskIdChange.NewId); + Assert.DoesNotContain(applicationMetadata.DataTypes, dataType => dataType.TaskId == metadata.TaskIdChange.OldId); + Assert.Contains(applicationMetadata.DataTypes, dataType => dataType.TaskId == metadata.TaskIdChange.NewId); } public static IEnumerable<object[]> UpsertProcessDefinitionAndNotifyTestData() diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutFileSyncTaskIdTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutFileSyncTaskIdTests.cs index 21734ea9202..b332cf1a1e7 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutFileSyncTaskIdTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutFileSyncTaskIdTests.cs @@ -10,7 +10,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -59,7 +58,7 @@ public async Task UpsertProcessDefinitionAndNotify_ShouldUpdateLayout_WhenRefere using var response = await HttpClient.PutAsync(url, form); // Assert - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string layoutFilePath = "App/ui/layoutSet2/layouts/layoutFile2InSet2.json"; string layoutContent = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, layoutFilePath); @@ -67,8 +66,8 @@ public async Task UpsertProcessDefinitionAndNotify_ShouldUpdateLayout_WhenRefere JsonNode layout = JsonSerializer.Deserialize<JsonNode>(layoutContent); string newTaskId = layout["data"]?["layout"]?[0]?["target"]?["taskId"]?.ToString(); - newTaskId.Should().Be(metadata.TaskIdChange.NewId); - newTaskId.Should().NotBe(metadata.TaskIdChange.OldId); + Assert.Equal(newTaskId, metadata.TaskIdChange.NewId); + Assert.NotEqual(newTaskId, metadata.TaskIdChange.OldId); } [Theory] @@ -105,10 +104,10 @@ public async Task UpsertProcessDefinitionAndNotify_ShouldNotUpdateLayout_WhenUnr using var response = await HttpClient.PutAsync(url, form); // Assert - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string layoutAfterUpdate = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, layoutPath); - layoutAfterUpdate.Should().Be(layoutBeforeUpdate); + Assert.Equal(layoutBeforeUpdate, layoutAfterUpdate); } public static IEnumerable<object[]> GetReferencedTaskIdTestData() diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutSetsFileSyncTaskIdTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutSetsFileSyncTaskIdTests.cs index d379af8c1b2..8f9028ee696 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutSetsFileSyncTaskIdTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutSetsFileSyncTaskIdTests.cs @@ -11,7 +11,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -50,15 +49,15 @@ public async Task UpsertProcessDefinition_ShouldSyncLayoutSets(string org, strin form.Add(new StringContent(metadataString, Encoding.UTF8, MediaTypeNames.Application.Json), "metadata"); using var response = await HttpClient.PutAsync(url, form); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string layoutSetsFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/ui/layout-sets.json"); LayoutSets layoutSets = JsonSerializer.Deserialize<LayoutSets>(layoutSetsFromRepo, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - layoutSets.Sets.Should().NotContain(layout => layout.Tasks.Contains(metadata.TaskIdChange.OldId)); - layoutSets.Sets.Should().Contain(layout => layout.Tasks.Contains(metadata.TaskIdChange.NewId)); + Assert.DoesNotContain(layoutSets.Sets, layout => layout.Tasks.Contains(metadata.TaskIdChange.OldId)); + Assert.Contains(layoutSets.Sets, layout => layout.Tasks.Contains(metadata.TaskIdChange.NewId)); } public static IEnumerable<object[]> UpsertProcessDefinitionAndNotifyTestData() diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/PolicyFileSyncTaskIdTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/PolicyFileSyncTaskIdTests.cs index 723a9b9daf7..4b558c1e656 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/PolicyFileSyncTaskIdTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/FileSync/TaskIdChangeTests/PolicyFileSyncTaskIdTests.cs @@ -10,7 +10,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -51,13 +50,13 @@ public async Task UpsertProcessDefinition_ShouldSyncLayoutSets(string org, strin form.Add(new StringContent(metadataString, Encoding.UTF8, MediaTypeNames.Application.Json), "metadata"); using var response = await HttpClient.PutAsync(url, form); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string policyFileFromRepo = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/authorization/policy.xml"); - policyFileFromRepo.Should().NotContain(metadata.TaskIdChange.OldId); - policyFileFromRepo.Should().Contain(metadata.TaskIdChange.NewId); + Assert.DoesNotContain(metadata.TaskIdChange.OldId, policyFileFromRepo); + Assert.Contains(metadata.TaskIdChange.NewId, policyFileFromRepo); } diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/GetProcessDefinitionTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/GetProcessDefinitionTests.cs index e0838b72cd3..0cc5ce61852 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/GetProcessDefinitionTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/GetProcessDefinitionTests.cs @@ -5,7 +5,6 @@ using System.Xml.Linq; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -32,13 +31,13 @@ public async Task GetProcessDefinitionTests_ShouldReturnOK(string org, string ap using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); XDocument responseXml = XDocument.Parse(responseContent); XDocument expectedXml = XDocument.Parse(fileContent); - XNode.DeepEquals(responseXml, expectedXml).Should().BeTrue(); + Assert.True(XNode.DeepEquals(responseXml, expectedXml)); } [Theory] @@ -49,7 +48,7 @@ public async Task GetProcessDefinitionTests_If_Doesnt_Exists_ShouldReturnNotFoun using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } private async Task<string> AddFileToRepo(string fileToCopyPath, string relativeCopyRepoLocation) diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/GetTemplatesTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/GetTemplatesTests.cs index dab92858d1f..4aded182962 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/GetTemplatesTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/GetTemplatesTests.cs @@ -3,7 +3,6 @@ using System.Net.Http; using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -27,14 +26,14 @@ public async Task GetTemplates_ShouldReturnOK(string org, string app, string ver string url = VersionPrefix(org, app, version); using var response = await HttpClient.GetAsync(url); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); List<string> responseContent = await response.Content.ReadAsAsync<List<string>>(); - responseContent.Count.Should().Be(expectedTemplates.Length); + Assert.Equal(expectedTemplates.Length, responseContent.Count); foreach (string expectedTemplate in expectedTemplates) { - responseContent.Should().Contain(expectedTemplate); + Assert.Contains(expectedTemplate, responseContent); } } } diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/ProcessDataTypesChangedNotifyTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/ProcessDataTypesChangedNotifyTests.cs index dc1a9dc609c..3316215a0d3 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/ProcessDataTypesChangedNotifyTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/ProcessDataTypesChangedNotifyTests.cs @@ -8,7 +8,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -38,7 +37,7 @@ public async Task ProcessDataTypesChangedNotify_ShouldReturnOk(string org, strin Content = new StringContent(metadataString, Encoding.UTF8, "application/json") }; using var response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); } public static IEnumerable<object[]> ProcessDataTypeChangedNotifyTestData() diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/SaveProcessDefinitionFromTemplateTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/SaveProcessDefinitionFromTemplateTests.cs index 5c600011774..5bbd5922405 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/SaveProcessDefinitionFromTemplateTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/SaveProcessDefinitionFromTemplateTests.cs @@ -3,7 +3,6 @@ using System.Xml.Linq; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -28,7 +27,7 @@ public async Task SaveProcessDefinitionFromTemplate_WrongTemplate_ShouldReturn40 string url = VersionPrefix(org, targetRepository, version, templateName); using var response = await HttpClient.PutAsync(url, null); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Theory] @@ -42,7 +41,7 @@ public async Task SaveProcessDefinitionFromTemplate_ShouldReturnOk_AndSaveTempla string url = VersionPrefix(org, targetRepository, version, templateName); using var response = await HttpClient.PutAsync(url, null); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string responseContent = await response.Content.ReadAsStringAsync(); @@ -50,7 +49,7 @@ public async Task SaveProcessDefinitionFromTemplate_ShouldReturnOk_AndSaveTempla XDocument responseXml = XDocument.Parse(responseContent); XDocument savedXml = XDocument.Parse(savedFile); - XNode.DeepEquals(savedXml, responseXml).Should().BeTrue(); + Assert.True(XNode.DeepEquals(savedXml, responseXml)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/UpsertProcessDefinitionAndNotifyTests.cs b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/UpsertProcessDefinitionAndNotifyTests.cs index 0cdfb92b6ee..6595f9d4936 100644 --- a/backend/tests/Designer.Tests/Controllers/ProcessModelingController/UpsertProcessDefinitionAndNotifyTests.cs +++ b/backend/tests/Designer.Tests/Controllers/ProcessModelingController/UpsertProcessDefinitionAndNotifyTests.cs @@ -11,7 +11,6 @@ using Altinn.Studio.Designer.Models.Dto; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -46,13 +45,13 @@ public async Task UpsertProcessDefinition_ShouldReturnOk(string org, string app, form.Add(new StringContent(metadataString, Encoding.UTF8, MediaTypeNames.Application.Json), "metadata"); using var response = await HttpClient.PutAsync(url, form); - response.StatusCode.Should().Be(HttpStatusCode.Accepted); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); string savedFile = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/process/process.bpmn"); XDocument expectedXml = XDocument.Parse(processContent); XDocument savedXml = XDocument.Parse(savedFile); - XNode.DeepEquals(savedXml, expectedXml).Should().BeTrue(); + Assert.True(XNode.DeepEquals(savedXml, expectedXml)); } public static IEnumerable<object[]> UpsertProcessDefinitionAndNotifyTestData() diff --git a/backend/tests/Designer.Tests/Controllers/ResourceAdminController/ResourceAdminControllerTestsBaseClass.cs b/backend/tests/Designer.Tests/Controllers/ResourceAdminController/ResourceAdminControllerTestsBaseClass.cs index eea732572e7..7e55e8b0be6 100644 --- a/backend/tests/Designer.Tests/Controllers/ResourceAdminController/ResourceAdminControllerTestsBaseClass.cs +++ b/backend/tests/Designer.Tests/Controllers/ResourceAdminController/ResourceAdminControllerTestsBaseClass.cs @@ -1,11 +1,9 @@ using System.Collections.Generic; -using Altinn.Studio.Designer.Configuration; using Altinn.Studio.Designer.Enums; using Altinn.Studio.Designer.Models; using Altinn.Studio.Designer.Services.Interfaces; using Altinn.Studio.Designer.TypedHttpClients.Altinn2Metadata; using Designer.Tests.Controllers.ApiTests; -using Designer.Tests.Mocks; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -38,7 +36,7 @@ protected static List<ResourceReference> GetTestResourceReferences() { List<ResourceReference> resourceReferences = new List<ResourceReference> { - new ResourceReference { Reference = string.Empty, ReferenceSource = ReferenceSource.Default, ReferenceType = ReferenceType.Default } + new ResourceReference { Reference = string.Empty, ReferenceSource = ResourceReferenceSource.Default, ReferenceType = ResourceReferenceType.Default } }; return resourceReferences; diff --git a/backend/tests/Designer.Tests/Controllers/TextController/DeleteLanguageTests.cs b/backend/tests/Designer.Tests/Controllers/TextController/DeleteLanguageTests.cs index c24565202a2..e5b9833001b 100644 --- a/backend/tests/Designer.Tests/Controllers/TextController/DeleteLanguageTests.cs +++ b/backend/tests/Designer.Tests/Controllers/TextController/DeleteLanguageTests.cs @@ -1,7 +1,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -22,16 +21,15 @@ public async Task DeleteLanguage_WithValidInput_ReturnsOk(string org, string app string targetRepository = TestDataHelper.GenerateTestRepoName(); await CopyRepositoryForTest(org, app, developer, targetRepository); string url = $"{VersionPrefix(org, targetRepository)}/language/{language}"; - TestDataHelper.FileExistsInRepo(org, targetRepository, developer, $"App/config/texts/resource.{language}.json") - .Should().BeTrue(); + Assert.True(TestDataHelper.FileExistsInRepo(org, targetRepository, developer, $"App/config/texts/resource.{language}.json")); // Act using var response = await HttpClient.DeleteAsync(url); // Assert Assert.Equal(200, (int)response.StatusCode); - TestDataHelper.FileExistsInRepo(org, targetRepository, developer, $"App/config/texts/resource.{language}.json") - .Should().BeFalse(); + Assert.False(TestDataHelper.FileExistsInRepo(org, targetRepository, developer, + $"App/config/texts/resource.{language}.json")); } } } diff --git a/backend/tests/Designer.Tests/Controllers/TextController/GetLanguagesTests.cs b/backend/tests/Designer.Tests/Controllers/TextController/GetLanguagesTests.cs index c7aca125b23..fe080071357 100644 --- a/backend/tests/Designer.Tests/Controllers/TextController/GetLanguagesTests.cs +++ b/backend/tests/Designer.Tests/Controllers/TextController/GetLanguagesTests.cs @@ -2,7 +2,6 @@ using System.Text.Json; using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -31,7 +30,7 @@ public async Task GetLanguage_WithValidInput_ReturnsOk(string org, string app, p Assert.Equal(HttpStatusCode.OK, response.StatusCode); string content = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedContent, content).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedContent, content)); } } } diff --git a/backend/tests/Designer.Tests/Controllers/TextController/GetResourceTests.cs b/backend/tests/Designer.Tests/Controllers/TextController/GetResourceTests.cs index 1764773cc1b..64e775f3981 100644 --- a/backend/tests/Designer.Tests/Controllers/TextController/GetResourceTests.cs +++ b/backend/tests/Designer.Tests/Controllers/TextController/GetResourceTests.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -30,7 +29,7 @@ public async Task GetLanguage_WithValidInput_ReturnsOk(string org, string app, s Assert.Equal(HttpStatusCode.OK, response.StatusCode); string content = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedContent, content).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(expectedContent, content)); } [Theory] diff --git a/backend/tests/Designer.Tests/Controllers/TextController/SaveResourceTests.cs b/backend/tests/Designer.Tests/Controllers/TextController/SaveResourceTests.cs index 03fc4589dba..eca03b7fb78 100644 --- a/backend/tests/Designer.Tests/Controllers/TextController/SaveResourceTests.cs +++ b/backend/tests/Designer.Tests/Controllers/TextController/SaveResourceTests.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -36,8 +35,8 @@ public async Task SaveResource_WithValidInput_ReturnsOk(string org, string app, // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); - TestDataHelper.FileExistsInRepo(org, targetRepository, developer, $"App/config/texts/resource.{lang}.json").Should().BeTrue(); - JsonUtils.DeepEquals(payload, TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/config/texts/resource.{lang}.json")).Should().BeTrue(); + Assert.True(TestDataHelper.FileExistsInRepo(org, targetRepository, developer, $"App/config/texts/resource.{lang}.json")); + Assert.True(JsonUtils.DeepEquals(payload, TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/config/texts/resource.{lang}.json"))); } diff --git a/backend/tests/Designer.Tests/Controllers/TextController/UpdateTextsForKeysTests.cs b/backend/tests/Designer.Tests/Controllers/TextController/UpdateTextsForKeysTests.cs index a3a9f827bb5..1a1041a21af 100644 --- a/backend/tests/Designer.Tests/Controllers/TextController/UpdateTextsForKeysTests.cs +++ b/backend/tests/Designer.Tests/Controllers/TextController/UpdateTextsForKeysTests.cs @@ -9,7 +9,6 @@ using Altinn.Studio.Designer.Models; using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using SharedResources.Tests; using Xunit; @@ -53,7 +52,7 @@ public async Task UpdateTextsForKeys_WithValidInput_ReturnsOk(string org, string // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); string actualContent = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/config/texts/resource.{lang}.json"); - JsonUtils.DeepEquals(JsonSerializer.Serialize(expectedResource, _jsonOptions), actualContent).Should().BeTrue(); + Assert.True(JsonUtils.DeepEquals(JsonSerializer.Serialize(expectedResource, _jsonOptions), actualContent)); } [Theory] @@ -74,7 +73,7 @@ public async Task UpdateTextsForKeys_ForTextsThatHaveVariables_MaintainsVariable Assert.Equal(HttpStatusCode.OK, response.StatusCode); string actualContent = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/config/texts/resource.{lang}.json"); TextResource actualResource = JsonSerializer.Deserialize<TextResource>(actualContent, _jsonOptions); - actualResource.Resources.Find(el => el.Id == "TextUsingVariables").Variables.Should().NotBeNull(); + Assert.NotNull(actualResource.Resources.Find(el => el.Id == "TextUsingVariables").Variables); } private static void PrepareExpectedResourceWithoutVariables(TextResource resource, Dictionary<string, string> updateDictionary) diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/GetAppScopesAsyncTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/GetAppScopesAsyncTests.cs index 8cd7c54919a..f981cc34e2f 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/GetAppScopesAsyncTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/GetAppScopesAsyncTests.cs @@ -4,7 +4,6 @@ using Altinn.Studio.Designer.Repository.Models.AppScope; using Designer.Tests.Fixtures; using Designer.Tests.Utils; -using FluentAssertions; using Xunit; namespace Designer.Tests.DbIntegrationTests.AppScopesRepository; @@ -22,7 +21,7 @@ public async Task GetAppScopesAsync_NoScopesInDb_ReturnsNull(string org, string var repository = new Altinn.Studio.Designer.Repository.ORMImplementation.AppScopesRepository(DbFixture.DbContext); var result = await repository.GetAppScopesAsync(AltinnRepoContext.FromOrgRepo(org, app)); - result.Should().BeNull(); + Assert.Null(result); } @@ -34,7 +33,7 @@ public async Task GetAppScopesAsync_ReturnExpected(string org, string app, int n await DbFixture.PrepareAppScopesEntityInDatabaseAsync(entity); var repository = new Altinn.Studio.Designer.Repository.ORMImplementation.AppScopesRepository(DbFixture.DbContext); AppScopesEntity result = await repository.GetAppScopesAsync(AltinnRepoContext.FromOrgRepo(org, app)); - result.Version.Should().BeGreaterThan(0); + Assert.True(result.Version > 0); entity.Version = result.Version; EntityAssertions.AssertEqual(result, entity); } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/UpsertAppScopesAsyncIntegrationTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/UpsertAppScopesAsyncIntegrationTests.cs index e09cc05d01a..5bfe663b03b 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/UpsertAppScopesAsyncIntegrationTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/UpsertAppScopesAsyncIntegrationTests.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Designer.Tests.Fixtures; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.EntityFrameworkCore; using Xunit; @@ -26,7 +25,7 @@ public async Task UpsertAppScopesAsync_ShouldCreateAppScopes_IfNotExists(string d.Org == org && d.App == app); - dbRecord.Version.Should().BeGreaterThan(0); + Assert.True(dbRecord.Version > 0); entity.Version = dbRecord.Version; EntityAssertions.AssertEqual(entity, dbRecord); @@ -43,7 +42,7 @@ public async Task UpsertAppScopesAsync_ShouldUpdateAppScopes_IfAlreadyExists(str d.Org == org && d.App == app); - dbRecord.Version.Should().BeGreaterThan(0); + Assert.True(dbRecord.Version > 0); entity.Version = dbRecord.Version; EntityAssertions.AssertEqual(entity, dbRecord); @@ -51,7 +50,7 @@ public async Task UpsertAppScopesAsync_ShouldUpdateAppScopes_IfAlreadyExists(str var repository = new Altinn.Studio.Designer.Repository.ORMImplementation.AppScopesRepository(DbFixture.DbContext); var result = await repository.UpsertAppScopesAsync(entity); - result.Version.Should().NotBe(entity.Version); + Assert.NotEqual(entity.Version, result.Version); entity.Version = result.Version; EntityAssertions.AssertEqual(entity, result); diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/Utils/AppScopesEntityAsserts.cs b/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/Utils/AppScopesEntityAsserts.cs index a7cafddcdf9..b87674246af 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/Utils/AppScopesEntityAsserts.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/AppScopesRepository/Utils/AppScopesEntityAsserts.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; using System.Text.Json; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Repository.Models.AppScope; -using FluentAssertions; +using Xunit; namespace Designer.Tests.DbIntegrationTests; @@ -10,28 +11,28 @@ public partial class EntityAssertions { public static void AssertEqual(AppScopesEntity appScopesEntity, Altinn.Studio.Designer.Repository.ORMImplementation.Models.AppScopesDbModel dbRecord) { - dbRecord.App.Should().BeEquivalentTo(appScopesEntity.App); - dbRecord.Org.Should().BeEquivalentTo(appScopesEntity.Org); - dbRecord.CreatedBy.Should().BeEquivalentTo(appScopesEntity.CreatedBy); - dbRecord.LastModifiedBy.Should().BeEquivalentTo(appScopesEntity.LastModifiedBy); - dbRecord.Created.Should().BeCloseTo(appScopesEntity.Created, TimeSpan.FromMilliseconds(100)); + Assert.Equal(dbRecord.App, appScopesEntity.App); + Assert.Equal(dbRecord.Org, appScopesEntity.Org); + Assert.Equal(dbRecord.CreatedBy, appScopesEntity.CreatedBy); + Assert.Equal(dbRecord.LastModifiedBy, appScopesEntity.LastModifiedBy); + AssertionUtil.AssertCloseTo(dbRecord.Created, appScopesEntity.Created, TimeSpan.FromMilliseconds(100)); - dbRecord.Version.Should().Be(appScopesEntity.Version); + Assert.Equal(dbRecord.Version, appScopesEntity.Version); var scopesFromDb = JsonSerializer.Deserialize<ISet<MaskinPortenScopeEntity>>(dbRecord.Scopes, JsonOptions); - scopesFromDb.Should().BeEquivalentTo(appScopesEntity.Scopes); - dbRecord.Version.Should().Be(appScopesEntity.Version); + AssertionUtil.AssertEqualTo(scopesFromDb, appScopesEntity.Scopes); + Assert.Equal(dbRecord.Version, appScopesEntity.Version); } public static void AssertEqual(AppScopesEntity expected, AppScopesEntity actual) { - actual.App.Should().Be(expected.App); - actual.Org.Should().Be(expected.Org); - actual.CreatedBy.Should().Be(expected.CreatedBy); - actual.LastModifiedBy.Should().Be(expected.LastModifiedBy); - actual.Created.Should().BeCloseTo(expected.Created, TimeSpan.FromMilliseconds(100)); + Assert.Equal(expected.App, actual.App); + Assert.Equal(expected.Org, actual.Org); + Assert.Equal(expected.CreatedBy, actual.CreatedBy); + Assert.Equal(expected.LastModifiedBy, actual.LastModifiedBy); + AssertionUtil.AssertCloseTo(expected.Created, actual.Created, TimeSpan.FromMilliseconds(100)); - actual.Version.Should().Be(expected.Version); - actual.Scopes.Should().BeEquivalentTo(expected.Scopes); + Assert.Equal(expected.Version, actual.Version); + AssertionUtil.AssertEqualTo(expected.Scopes, actual.Scopes); } } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Base/DeploymentEntityIntegrationTestsBase.cs b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Base/DeploymentEntityIntegrationTestsBase.cs index d84164a0261..9faf097ccf8 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Base/DeploymentEntityIntegrationTestsBase.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Base/DeploymentEntityIntegrationTestsBase.cs @@ -47,6 +47,7 @@ private Altinn.Studio.Designer.Repository.ORMImplementation.Models.DeploymentDbM App = entity.App, Buildresult = entity.Build.Result.ToEnumMemberAttributeValue(), Created = entity.Created, + CreatedBy = entity.CreatedBy, Entity = JsonSerializer.Serialize(entity, JsonOptions), EnvName = entity.EnvName, Build = MapBuildToDbModel(entity.Build) diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/CreateIntegrationTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/CreateIntegrationTests.cs index d9295cf9dd0..d72a9a27537 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/CreateIntegrationTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/CreateIntegrationTests.cs @@ -4,7 +4,6 @@ using Altinn.Studio.Designer.Repository.ORMImplementation.Models; using Designer.Tests.DbIntegrationTests.DeploymentEntityRepository.Base; using Designer.Tests.Fixtures; -using FluentAssertions; using Microsoft.EntityFrameworkCore; using Xunit; @@ -29,8 +28,7 @@ public async Task Create_ShouldInsertRecordInDatabase(string org) d.App == deploymentEntity.App && d.Buildid == buildId.ToString()); - - dbRecord.DeploymentType.Should().Be(DeploymentType.Deploy); + Assert.Equal(DeploymentType.Deploy, dbRecord.DeploymentType); EntityAssertions.AssertEqual(deploymentEntity, dbRecord); } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/GetIntegrationTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/GetIntegrationTests.cs index 9f9cd0d1cd7..cf2c94996be 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/GetIntegrationTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/GetIntegrationTests.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Altinn.Studio.Designer.Repository.Models; using Altinn.Studio.Designer.Repository.ORMImplementation; using Altinn.Studio.Designer.ViewModels.Request; using Altinn.Studio.Designer.ViewModels.Request.Enums; using Designer.Tests.DbIntegrationTests.DeploymentEntityRepository.Base; using Designer.Tests.Fixtures; -using FluentAssertions; using Xunit; namespace Designer.Tests.DbIntegrationTests.DeploymentEntityRepository; @@ -36,11 +36,18 @@ public async Task Get_ShouldReturnCorrectRecordsFromDatabase(string org, string .Take(top) .ToList(); - result.Count.Should().Be(top); - result.Should().BeEquivalentTo(expectedEntities, options => - options.Using<DateTime>(ctx => - ctx.Subject.Should().BeCloseTo(ctx.Expectation, TimeSpan.FromMilliseconds(200)) - ).WhenTypeIs<DateTime>()); + Assert.Equal(top, result.Count); + Assert.Equal(top, result.Count); + + + expectedEntities.ToHashSet().SetEquals(result.ToHashSet()); + var compareList = expectedEntities.Zip(result, (expected, actual) => + { + EntityAssertions.AssertEqual(expected, actual, TimeSpan.FromMilliseconds(200)); + return true; + }).ToList(); + + Assert.All(compareList, Assert.True); } [Theory] @@ -65,11 +72,15 @@ public async Task Get_Without_TopDefined_ShouldReturnAllRecordsForGivenApp(strin : deploymentEntities.OrderByDescending(d => d.Created)) .ToList(); - result.Count.Should().Be(allEntitiesCount); - result.Should().BeEquivalentTo(expectedEntities, options => - options.Using<DateTime>(ctx => - ctx.Subject.Should().BeCloseTo(ctx.Expectation, TimeSpan.FromMilliseconds(200)) - ).WhenTypeIs<DateTime>()); + Assert.Equal(allEntitiesCount, result.Count); + + var compareList = expectedEntities.Zip(result, (expected, actual) => + { + EntityAssertions.AssertEqual(expected, actual, TimeSpan.FromMilliseconds(200)); + return true; + }).ToList(); + + Assert.All(compareList, Assert.True); } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/GetSingleIntegrationTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/GetSingleIntegrationTests.cs index e1c88912b39..60b7a58a1f0 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/GetSingleIntegrationTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/GetSingleIntegrationTests.cs @@ -3,7 +3,6 @@ using Altinn.Studio.Designer.Repository.ORMImplementation; using Designer.Tests.DbIntegrationTests.DeploymentEntityRepository.Base; using Designer.Tests.Fixtures; -using FluentAssertions; using Xunit; namespace Designer.Tests.DbIntegrationTests.DeploymentEntityRepository; @@ -24,9 +23,6 @@ public async Task Get_ShouldReturnRecordFromDatabase(string org) var repository = new DeploymentRepository(DbFixture.DbContext); var result = await repository.Get(deploymentEntity.Org, deploymentEntity.Build.Id); - result.Should().BeEquivalentTo(deploymentEntity, options => - options.Using<DateTime>(ctx => - ctx.Subject.Should().BeCloseTo(ctx.Expectation, TimeSpan.FromMilliseconds(200)) - ).WhenTypeIs<DateTime>()); + EntityAssertions.AssertEqual(deploymentEntity, result, TimeSpan.FromMilliseconds(200)); } } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Utils/DeploymentEntityAsserts.cs b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Utils/DeploymentEntityAsserts.cs index 0b106ab4c05..58eb7ca6d51 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Utils/DeploymentEntityAsserts.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Utils/DeploymentEntityAsserts.cs @@ -1,8 +1,10 @@ using System; using System.Text.Json; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Repository.Models; using Altinn.Studio.Designer.Repository.ORMImplementation.Models; -using FluentAssertions; +using Altinn.Studio.Designer.TypedHttpClients.AzureDevOps.Enums; +using Xunit; namespace Designer.Tests.DbIntegrationTests; @@ -10,30 +12,64 @@ public static partial class EntityAssertions { public static void AssertEqual(DeploymentEntity deploymentEntity, Altinn.Studio.Designer.Repository.ORMImplementation.Models.DeploymentDbModel dbRecord) { - dbRecord.App.Should().BeEquivalentTo(deploymentEntity.App); - dbRecord.Org.Should().BeEquivalentTo(deploymentEntity.Org); - dbRecord.Buildid.Should().BeEquivalentTo(deploymentEntity.Build.Id); - dbRecord.Buildresult.Should().BeEquivalentTo(deploymentEntity.Build.Result.ToString()); - dbRecord.Tagname.Should().BeEquivalentTo(deploymentEntity.TagName); - dbRecord.EnvName.Should().BeEquivalentTo(deploymentEntity.EnvName); + Assert.Equal(dbRecord.App, deploymentEntity.App); + Assert.Equal(dbRecord.Org, deploymentEntity.Org); + Assert.Equal(dbRecord.CreatedBy, deploymentEntity.CreatedBy); + Assert.Equal(dbRecord.Buildid, deploymentEntity.Build.Id); + Assert.Equal(dbRecord.Buildresult, deploymentEntity.Build.Result.ToEnumMemberAttributeValue()); + Assert.Equal(dbRecord.Tagname, deploymentEntity.TagName); + Assert.Equal(dbRecord.EnvName, deploymentEntity.EnvName); var entityFromColumn = JsonSerializer.Deserialize<DeploymentEntity>(dbRecord.Entity, JsonOptions); - entityFromColumn.Should().BeEquivalentTo(deploymentEntity); + AssertionUtil.AssertEqualTo(deploymentEntity, entityFromColumn); Altinn.Studio.Designer.Repository.ORMImplementation.Models.BuildDbModel buildDbModel = dbRecord.Build; - buildDbModel.ExternalId.Should().BeEquivalentTo(deploymentEntity.Build.Id); - buildDbModel.Status.Should().BeEquivalentTo(deploymentEntity.Build.Status.ToString()); - buildDbModel.Result.Should().BeEquivalentTo(deploymentEntity.Build.Result.ToString()); - buildDbModel.BuildType.Should().Be(BuildType.Deployment); + Assert.Equal(buildDbModel.ExternalId, deploymentEntity.Build.Id); + Assert.Equal(buildDbModel.Status, deploymentEntity.Build.Status.ToString()); + Assert.Equal(buildDbModel.Result, deploymentEntity.Build.Result.ToString()); + Assert.Equal(BuildType.Deployment, buildDbModel.BuildType); - buildDbModel.Started!.Value.UtcDateTime.Should().BeCloseTo(deploymentEntity.Build.Started!.Value, TimeSpan.FromMilliseconds(100)); + AssertionUtil.AssertCloseTo(buildDbModel.Started!.Value.UtcDateTime, deploymentEntity.Build.Started!.Value, TimeSpan.FromMilliseconds(100)); if (!buildDbModel.Finished.HasValue) { - deploymentEntity.Build.Finished.Should().BeNull(); + Assert.Null(deploymentEntity.Build.Finished); } else { - buildDbModel.Finished!.Value.UtcDateTime.Should().BeCloseTo(deploymentEntity.Build.Finished!.Value, TimeSpan.FromMilliseconds(100)); + AssertionUtil.AssertCloseTo(buildDbModel.Finished!.Value.UtcDateTime, deploymentEntity.Build.Finished!.Value, TimeSpan.FromMilliseconds(100)); + } + } + + public static void AssertEqual(DeploymentEntity expected, DeploymentEntity actual, TimeSpan datesTolerance) + { + Assert.Equal(expected.App, actual.App); + Assert.Equal(expected.Org, actual.Org); + Assert.Equal(expected.CreatedBy, actual.CreatedBy); + AssertionUtil.AssertCloseTo(expected.Created, actual.Created, datesTolerance); + Assert.Equal(expected.TagName, actual.TagName); + Assert.Equal(expected.EnvName, actual.EnvName); + Assert.Equal(expected.Build.Id, actual.Build.Id); + Assert.Equal(expected.Build.Status, actual.Build.Status); + Assert.Equal(expected.Build.Result, actual.Build.Result); + + if (!expected.Build.Started.HasValue) + { + Assert.Null(expected.Build.Started); + Assert.Null(actual.Build.Started); + } + else + { + AssertionUtil.AssertCloseTo(expected.Build.Started.Value, actual.Build.Started.Value, datesTolerance); + } + + if (!expected.Build.Finished.HasValue) + { + Assert.Null(expected.Build.Finished); + Assert.Null(actual.Build.Finished); + } + else + { + AssertionUtil.AssertCloseTo(expected.Build.Finished.Value, actual.Build.Finished.Value, datesTolerance); } } } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Utils/EntityGenerationUtils.cs b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Utils/EntityGenerationUtils.cs index 9154fbd3405..29f2259e21b 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Utils/EntityGenerationUtils.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/DeploymentEntityRepository/Utils/EntityGenerationUtils.cs @@ -22,6 +22,7 @@ public static DeploymentEntity GenerateDeploymentEntity(string org, string app = TagName = tagname ?? Guid.NewGuid().ToString(), EnvName = Guid.NewGuid().ToString(), Created = DateTime.UtcNow, + CreatedBy = "testUser" }; } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetBuildStatusAndResultsFilterIntegrationTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetBuildStatusAndResultsFilterIntegrationTests.cs index 2163ce15468..fc7743541b6 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetBuildStatusAndResultsFilterIntegrationTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetBuildStatusAndResultsFilterIntegrationTests.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Repository.ORMImplementation; using Altinn.Studio.Designer.TypedHttpClients.AzureDevOps.Enums; using Designer.Tests.DbIntegrationTests.ReleaseEntityRepository.Base; using Designer.Tests.Fixtures; -using FluentAssertions; using Xunit; namespace Designer.Tests.DbIntegrationTests.ReleaseEntityRepository; @@ -42,8 +42,8 @@ public async Task Get_ShouldReturnCorrectRecordsFromDatabase(string org, string var results = (await repository.Get(org, app, tagName, buildStatuses, buildResults)).ToList(); - results.Should().HaveCount(expectedFoundNumber); - results.Should().BeEquivalentTo(exptectedEntities); + Assert.Equal(expectedFoundNumber, results.Count); + AssertionUtil.AssertEqualTo(exptectedEntities, results); } public static IEnumerable<object[]> TestData() diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetSingleIntegrationTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetSingleIntegrationTests.cs index 2957eab1497..624b3e4c0be 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetSingleIntegrationTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetSingleIntegrationTests.cs @@ -1,10 +1,10 @@ using System; using System.Linq; using System.Threading.Tasks; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Repository.ORMImplementation; using Designer.Tests.DbIntegrationTests.ReleaseEntityRepository.Base; using Designer.Tests.Fixtures; -using FluentAssertions; using Xunit; namespace Designer.Tests.DbIntegrationTests.ReleaseEntityRepository; @@ -25,6 +25,6 @@ public async Task GetSingleAsync_WhenCalled_ShouldReturnSingleReleaseEntity(stri await PrepareEntityInDatabase(releaseEntity); var result = (await repository.Get(releaseEntity.Org, buildId.ToString())).Single(); - result.Should().BeEquivalentTo(releaseEntity); + AssertionUtil.AssertEqualTo(releaseEntity, result); } } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetSucceededReleaseFromDbIntegrationTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetSucceededReleaseFromDbIntegrationTests.cs index aefbacfd4f1..be7c6c5507d 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetSucceededReleaseFromDbIntegrationTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetSucceededReleaseFromDbIntegrationTests.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Repository.ORMImplementation; using Altinn.Studio.Designer.TypedHttpClients.AzureDevOps.Enums; using Designer.Tests.DbIntegrationTests.ReleaseEntityRepository.Base; using Designer.Tests.Fixtures; -using FluentAssertions; using Xunit; namespace Designer.Tests.DbIntegrationTests.ReleaseEntityRepository; @@ -41,7 +41,7 @@ public async Task Get_ShouldReturnCorrectRecordsFromDatabase(string org, string r.Build.Result == BuildResult.Succeeded); var result = await repository.GetSucceededReleaseFromDb(org, app, tagName); - result.Should().BeEquivalentTo(exptectedEntity); + AssertionUtil.AssertEqualTo(exptectedEntity, result); } public static IEnumerable<object[]> TestData() diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetTopAndSortedIntegrationTests.cs b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetTopAndSortedIntegrationTests.cs index f5be7080a23..1f4391b0011 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetTopAndSortedIntegrationTests.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/GetTopAndSortedIntegrationTests.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Repository.ORMImplementation; using Altinn.Studio.Designer.ViewModels.Request; using Altinn.Studio.Designer.ViewModels.Request.Enums; using Designer.Tests.DbIntegrationTests.ReleaseEntityRepository.Base; using Designer.Tests.Fixtures; -using FluentAssertions; using Xunit; namespace Designer.Tests.DbIntegrationTests.ReleaseEntityRepository; @@ -36,8 +36,8 @@ public async Task Get_ShouldReturnCorrectRecordsFromDatabase(string org, string .Take(top) .ToList(); - result.Count.Should().Be(top); - result.Should().BeEquivalentTo(expectedEntities); + Assert.Equal(top, result.Count); + AssertionUtil.AssertEqualTo(expectedEntities, result); } [Theory] @@ -58,12 +58,12 @@ public async Task Get_Without_TopDefined_ShouldReturnAllRecordsForGivenApp(strin var result = (await repository.Get(org, app, query)).ToList(); var expectedEntities = (sortDirection == SortDirection.Ascending - ? releaseEntities.OrderBy(d => d.Created) - : releaseEntities.OrderByDescending(d => d.Created)) + ? releaseEntities.OrderBy(r => r.Created) + : releaseEntities.OrderByDescending(r => r.Created)) .ToList(); - result.Count().Should().Be(allEntitiesCount); - result.Should().BeEquivalentTo(expectedEntities); + Assert.Equal(allEntitiesCount, result.Count); + AssertionUtil.AssertEqualTo(expectedEntities, result); } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/Utils/EntityGenerationUtils.cs b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/Utils/EntityGenerationUtils.cs index d6093eaa3f7..b386824fc59 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/Utils/EntityGenerationUtils.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/Utils/EntityGenerationUtils.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using Altinn.Studio.Designer.Repository.Models; using Altinn.Studio.Designer.TypedHttpClients.AzureDevOps.Enums; @@ -28,6 +29,10 @@ public static ReleaseEntity GenerateReleaseEntity(string org, string app = null, public static IEnumerable<ReleaseEntity> GenerateReleaseEntities(string org, string app, int count) => Enumerable.Range(0, count) - .Select(x => GenerateReleaseEntity(org, app)).ToList(); + .Select(x => + { + Thread.Sleep(1); // To ensure unique timestamps + return GenerateReleaseEntity(org, app); + }).ToList(); } } diff --git a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/Utils/ReleaseEntityAsserts.cs b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/Utils/ReleaseEntityAsserts.cs index 329e2078c26..4f4b958c843 100644 --- a/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/Utils/ReleaseEntityAsserts.cs +++ b/backend/tests/Designer.Tests/DbIntegrationTests/ReleaseEntityRepository/Utils/ReleaseEntityAsserts.cs @@ -1,7 +1,9 @@ using System; using System.Text.Json; +using Altinn.AccessManagement.Tests.Utils; using Altinn.Studio.Designer.Repository.Models; -using FluentAssertions; +using Altinn.Studio.Designer.TypedHttpClients.AzureDevOps.Enums; +using Xunit; namespace Designer.Tests.DbIntegrationTests; @@ -10,14 +12,14 @@ public static partial class EntityAssertions public static void AssertEqual(ReleaseEntity releaseEntity, Altinn.Studio.Designer.Repository.ORMImplementation.Models.ReleaseDbModel dbRecord) { - dbRecord.App.Should().BeEquivalentTo(releaseEntity.App); - dbRecord.Org.Should().BeEquivalentTo(releaseEntity.Org); - dbRecord.Buildid.Should().BeEquivalentTo(releaseEntity.Build.Id); - dbRecord.Buildresult.Should().BeEquivalentTo(releaseEntity.Build.Result.ToString()); - dbRecord.Tagname.Should().BeEquivalentTo(releaseEntity.TagName); + Assert.Equal(dbRecord.App, releaseEntity.App); + Assert.Equal(dbRecord.Org, releaseEntity.Org); + Assert.Equal(dbRecord.Buildid, releaseEntity.Build.Id); + Assert.Equal(dbRecord.Buildresult, releaseEntity.Build.Result.ToEnumMemberAttributeValue()); + Assert.Equal(dbRecord.Tagname, releaseEntity.TagName); var entityFromColumn = JsonSerializer.Deserialize<ReleaseEntity>(dbRecord.Entity, JsonOptions); - entityFromColumn.Should().BeEquivalentTo(releaseEntity); + AssertionUtil.AssertEqualTo(entityFromColumn, releaseEntity); - dbRecord.Created.Should().BeCloseTo(releaseEntity.Created, TimeSpan.FromMilliseconds(100)); + AssertionUtil.AssertCloseTo(dbRecord.Created, releaseEntity.Created, TimeSpan.FromMilliseconds(100)); } } diff --git a/backend/tests/Designer.Tests/Designer.Tests.csproj b/backend/tests/Designer.Tests/Designer.Tests.csproj index 3598a50a8dd..021a2522df7 100644 --- a/backend/tests/Designer.Tests/Designer.Tests.csproj +++ b/backend/tests/Designer.Tests/Designer.Tests.csproj @@ -14,7 +14,6 @@ <PackageReference Include="Altinn.ApiClients.Maskinporten" /> <PackageReference Include="Basic.Reference.Assemblies" /> <PackageReference Include="DistributedLock.FileSystem" /> - <PackageReference Include="FluentAssertions" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" /> <PackageReference Include="Microsoft.NET.Test.Sdk" /> diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/GiteaIntegrationTestsBase.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/GiteaIntegrationTestsBase.cs index cd0eb79e10e..41fe3264c23 100644 --- a/backend/tests/Designer.Tests/GiteaIntegrationTests/GiteaIntegrationTestsBase.cs +++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/GiteaIntegrationTestsBase.cs @@ -7,7 +7,6 @@ using Designer.Tests.Controllers.ApiTests; using Designer.Tests.Fixtures; using DotNet.Testcontainers.Builders; -using FluentAssertions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing.Handlers; using Microsoft.AspNetCore.TestHost; @@ -182,7 +181,7 @@ protected async Task CreateAppUsingDesigner(string org, string repoName) $"designer/api/repos/create-app?org={org}&repository={repoName}"); using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); } protected static string GetCommitInfoJson(string text, string org, string repository) => diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/CopyAppGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/CopyAppGiteaIntegrationTests.cs index 783143343aa..8875c821f77 100644 --- a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/CopyAppGiteaIntegrationTests.cs +++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/CopyAppGiteaIntegrationTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Designer.Tests.Fixtures; using Designer.Tests.Utils; -using FluentAssertions; using Xunit; namespace Designer.Tests.GiteaIntegrationTests.RepositoryController @@ -31,13 +30,13 @@ public async Task Copy_Repo_Should_Return_Created(string org, string targetOrg) CopyRepoName = TestDataHelper.GenerateTestRepoName("-gitea-copy"); - // Copy app + // Copy app1 using HttpResponseMessage commitResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/copy-app?sourceRepository={targetRepo}&targetRepository={CopyRepoName}&targetOrg={TargetCopyOrg}", null); - commitResponse.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, commitResponse.StatusCode); // Check if repo exists in git using HttpResponseMessage response = await GiteaFixture.GiteaClient.Value.GetAsync($"repos/{TargetCopyOrg}/{CopyRepoName}"); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } protected override void Dispose(bool disposing) diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitDiffIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitDiffIntegrationTests.cs index bef65fc4226..ce4953c2b17 100644 --- a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitDiffIntegrationTests.cs +++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitDiffIntegrationTests.cs @@ -9,7 +9,6 @@ using Designer.Tests.Fixtures; using Designer.Tests.Services; using Designer.Tests.Utils; -using FluentAssertions; using SharedResources.Tests; using Xunit; using Xunit.Abstractions; @@ -43,14 +42,15 @@ public async Task GetGitDiff_ShouldReturnOkWithDiff(string org) Content = new StringContent($"\"{newLayoutSetName}\"", Encoding.UTF8, MediaTypeNames.Application.Json) }; var updateLayoutSetNameResponse = await HttpClient.SendAsync(httpRequestMessageWithNewLayoutSetName); - updateLayoutSetNameResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, updateLayoutSetNameResponse.StatusCode); string getGitDiffUrl = $"designer/api/repos/repo/{org}/{targetRepo}/diff"; using var httpRequestMessageGetGitDiff = new HttpRequestMessage(HttpMethod.Get, getGitDiffUrl); var gitDiffResponse = await HttpClient.SendAsync(httpRequestMessageGetGitDiff); string responseContent = await gitDiffResponse.Content.ReadAsStringAsync(); - gitDiffResponse.StatusCode.Should().Be(HttpStatusCode.OK); - JsonUtils.DeepEquals(expectedGitDiffResponse, responseContent).Should().BeTrue(); + + Assert.Equal(HttpStatusCode.OK, gitDiffResponse.StatusCode); + Assert.True(JsonUtils.DeepEquals(expectedGitDiffResponse, responseContent)); } } diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitNotesGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitNotesGiteaIntegrationTests.cs index fb06664c4b4..6ff9464a899 100644 --- a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitNotesGiteaIntegrationTests.cs +++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/GitNotesGiteaIntegrationTests.cs @@ -8,7 +8,6 @@ using Altinn.Studio.Designer.Models; using Designer.Tests.Fixtures; using Designer.Tests.Utils; -using FluentAssertions; using Xunit; namespace Designer.Tests.GiteaIntegrationTests.RepositoryController @@ -30,10 +29,10 @@ public async Task Commit_AndPush_Separate_Should_Create_GitNote(string org) await File.WriteAllTextAsync($"{CreatedFolderPath}/test3.txt", "I am a new file"); using var commitContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit", commitContent); - commitResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, commitResponse.StatusCode); using HttpResponseMessage pushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/push", null); - pushResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, pushResponse.StatusCode); await VerifyStudioNoteAddedToLatestCommit(org, targetRepo); } @@ -50,7 +49,7 @@ public async Task Commit_AndPush_AndContents_Should_Create_GitNote(string org) using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent); - commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, commitAndPushResponse.StatusCode); await VerifyStudioNoteAddedToLatestCommit(org, targetRepo); } @@ -67,24 +66,24 @@ public async Task Commit_AndPush_AndContents_WorksAfterResetOfRepo(string org) using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent); - commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, commitAndPushResponse.StatusCode); await VerifyStudioNoteAddedToLatestCommit(org, targetRepo); // reset repo using HttpResponseMessage resetResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/reset"); - resetResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, resetResponse.StatusCode); // this ensures local clone using HttpResponseMessage appDevelopmentIndes = await HttpClient.GetAsync($"editor/{org}/{targetRepo}"); - appDevelopmentIndes.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, appDevelopmentIndes.StatusCode); // Try to create a new commit await File.WriteAllTextAsync($"{CreatedFolderPath}/newFile.txt", "I am a new file"); using var commitAndPushContent2 = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitAndPushResponse2 = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent2); - commitAndPushResponse2.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, commitAndPushResponse2.StatusCode); await VerifyStudioNoteAddedToLatestCommit(org, targetRepo); } @@ -100,17 +99,17 @@ public async Task LocalAndStudioDevelopment_PullLocalCommitFirst_BehaveAsExpecte // Create a file using gitea client using var createFileContent = new StringContent(GenerateCommitJsonPayload("I am a new file created in gitea", "test gitea commit"), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage createFileResponse = await GiteaFixture.GiteaClient.Value.PostAsync($"repos/{org}/{targetRepo}/contents/test2.txt", createFileContent); - createFileResponse.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, createFileResponse.StatusCode); // Try pull file with designer endpoint using HttpResponseMessage pullResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/pull"); - pullResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, pullResponse.StatusCode); // Add a new file and try to push with designer await File.WriteAllTextAsync($"{CreatedFolderPath}/test3.txt", "I am a new file created directly with gitea"); using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent); - commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, commitAndPushResponse.StatusCode); await VerifyStudioNoteAddedToLatestCommit(org, targetRepo); } @@ -125,19 +124,19 @@ public async Task LocalAndStudioDevelopment_BeginEditAndPullLocalCommit(string o // Create a file using gitea client using var createFileContent = new StringContent(GenerateCommitJsonPayload("I am a new file created in gitea", "test gitea commit"), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage createFileResponse = await GiteaFixture.GiteaClient.Value.PostAsync($"repos/{org}/{targetRepo}/contents/test2.txt", createFileContent); - createFileResponse.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, createFileResponse.StatusCode); // Add a new file and try to push with designer await File.WriteAllTextAsync($"{CreatedFolderPath}/test3.txt", "I am a new file created directly with gitea"); // Try pull file with designer endpoint using HttpResponseMessage pullResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/pull"); - pullResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, pullResponse.StatusCode); using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent); - commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, commitAndPushResponse.StatusCode); await VerifyStudioNoteAddedToLatestCommit(org, targetRepo); } @@ -150,7 +149,7 @@ private async Task VerifyStudioNoteAddedToLatestCommit(string org, string target var noteResponse = await GiteaFixture.GiteaClient.Value.GetAsync($"repos/{org}/{targetRepo}/git/notes/{commit.Sha}"); var notesNode = JsonNode.Parse(await noteResponse.Content.ReadAsStringAsync()); - notesNode!["message"]!.ToString().Should().Be("studio-commit"); + Assert.Equal("studio-commit", notesNode!["message"]!.ToString()); } } } diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/RepositoryControllerGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/RepositoryControllerGiteaIntegrationTests.cs index dcceb80f7f1..59a2dc213c7 100644 --- a/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/RepositoryControllerGiteaIntegrationTests.cs +++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/RepositoryController/RepositoryControllerGiteaIntegrationTests.cs @@ -12,7 +12,6 @@ using Altinn.Studio.Designer.RepositoryClient.Model; using Designer.Tests.Fixtures; using Designer.Tests.Utils; -using FluentAssertions; using Polly; using Polly.Retry; using Xunit; @@ -39,7 +38,7 @@ public async Task CreateRepo_ShouldBeAsExpected(string org) // Check if repo is created in gitea var giteaResponse = await GiteaFixture.GiteaClient.Value.GetAsync($"repos/{org}/{targetRepo}"); - giteaResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, giteaResponse.StatusCode); } [Theory] @@ -54,15 +53,15 @@ public async Task Commit_AndPush_AndContents_ShouldBeAsExpected(string org) using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent); - commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, commitAndPushResponse.StatusCode); // Check if file is pushed to gitea var giteaFileResponse = await GiteaFixture.GiteaClient.Value.GetAsync($"repos/{org}/{targetRepo}/contents/test.txt"); - giteaFileResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, giteaFileResponse.StatusCode); // Check contents with designer endpoint using HttpResponseMessage contentsResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/contents?path=test.txt"); - contentsResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, contentsResponse.StatusCode); } [Theory] @@ -76,14 +75,14 @@ public async Task Commit_AndPush_Separate_ShouldBeAsExpected(string org) await File.WriteAllTextAsync($"{CreatedFolderPath}/test3.txt", "I am a new file"); using var commitContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit", commitContent); - commitResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, commitResponse.StatusCode); using HttpResponseMessage pushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/push", null); - pushResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, pushResponse.StatusCode); // Check if file is pushed to gitea var giteaFileResponse2 = await GiteaFixture.GiteaClient.Value.GetAsync($"repos/{org}/{targetRepo}/contents/test3.txt"); - giteaFileResponse2.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, giteaFileResponse2.StatusCode); } [Theory] @@ -96,14 +95,14 @@ public async Task Pull_ShouldBeAsExpected(string org) // Create a file in gitea using var createFileContent = new StringContent(GenerateCommitJsonPayload("I am a new file created in gitea", "test commit"), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage createFileResponse = await GiteaFixture.GiteaClient.Value.PostAsync($"repos/{org}/{targetRepo}/contents/test2.txt", createFileContent); - createFileResponse.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, createFileResponse.StatusCode); // Try pull file with designer endpoint using HttpResponseMessage pullResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/pull"); - pullResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, pullResponse.StatusCode); // Check if file exists locally - File.Exists($"{CreatedFolderPath}/test2.txt").Should().BeTrue(); + Assert.True(File.Exists($"{CreatedFolderPath}/test2.txt")); } [Theory] @@ -115,9 +114,9 @@ public async Task Initial_Commit_ShouldBeAsExpected(string org) // Check initial-commit endpoint using HttpResponseMessage initialCommitResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/initial-commit"); - initialCommitResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, initialCommitResponse.StatusCode); var commit = await initialCommitResponse.Content.ReadAsAsync<Commit>(); - commit.Message.Should().Contain("App created"); + Assert.Contains("App created", commit.Message); } [Theory] @@ -129,15 +128,15 @@ public async Task MetadataAndStatus_ShouldBehaveAsExpected(string org) // Call metadata endpoint using HttpResponseMessage metadataResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/metadata"); - metadataResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, metadataResponse.StatusCode); var deserializedRepositoryModel = await metadataResponse.Content.ReadAsAsync<Repository>(); - deserializedRepositoryModel.Name.Should().Be(targetRepo); + Assert.Equal(targetRepo, deserializedRepositoryModel.Name); // Call status endpoint using HttpResponseMessage statusResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/status"); - statusResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, statusResponse.StatusCode); var deserializedRepoStatusModel = await statusResponse.Content.ReadAsAsync<RepoStatus>(); - deserializedRepoStatusModel.RepositoryStatus.Should().Be(RepositoryStatus.Ok); + Assert.Equal(RepositoryStatus.Ok, deserializedRepoStatusModel.RepositoryStatus); } [Theory] @@ -146,7 +145,7 @@ public async Task RepoStatus_ShouldReturn404NotFoundWhenInvalidRepo(string org) { // Call status endpoint using HttpResponseMessage statusResponse = await HttpClient.GetAsync($"designer/api/repos/repo/{org}/123/status"); - statusResponse.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, statusResponse.StatusCode); } [Theory] @@ -158,10 +157,11 @@ public async Task GetOrgRepos_ShouldBehaveAsExpected(string org) // Call getOrgRepos endpoint using HttpResponseMessage getOrgReposResponse = await HttpClient.GetAsync($"designer/api/repos/org/{org}"); - getOrgReposResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, getOrgReposResponse.StatusCode); var deserializedRepositoryModel = await getOrgReposResponse.Content.ReadAsAsync<List<Repository>>(); - deserializedRepositoryModel.Should().NotBeEmpty(); - deserializedRepositoryModel.Should().Contain(x => x.Name == targetRepo); + + Assert.NotEmpty(deserializedRepositoryModel); + Assert.Contains(deserializedRepositoryModel, x => x.Name == targetRepo); } // Get branch endpoint test @@ -174,14 +174,14 @@ public async Task GetBranches_And_Branch_ShouldBehaveAsExpected(string org) // Call branches endpoint using HttpResponseMessage branchesResponse = await _giteaRetryPolicy.ExecuteAsync(async () => await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/branches")); - branchesResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, branchesResponse.StatusCode); var deserializedBranchesModel = await branchesResponse.Content.ReadAsAsync<List<Branch>>(); - deserializedBranchesModel.Count.Should().Be(1); - deserializedBranchesModel.First().Name.Should().Be("master"); + Assert.Single(deserializedBranchesModel); + Assert.Equal("master", deserializedBranchesModel.First().Name); // Call branch endpoint using HttpResponseMessage branchResponse = await _giteaRetryPolicy.ExecuteAsync(async () => await HttpClient.GetAsync($"designer/api/repos/repo/{org}/{targetRepo}/branches/branch?branch=master")); - branchResponse.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, branchResponse.StatusCode); } @@ -195,13 +195,13 @@ public async Task PushWithConflictingChangesRemotely_ShouldReturnConflict(string // Create a file in gitea using var createFileContent = new StringContent(GenerateCommitJsonPayload("I am a new file created in gitea", "test commit"), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage createFileResponse = await GiteaFixture.GiteaClient.Value.PostAsync($"repos/{org}/{targetRepo}/contents/fileAlreadyInRepository.txt", createFileContent); - createFileResponse.StatusCode.Should().Be(HttpStatusCode.Created); + Assert.Equal(HttpStatusCode.Created, createFileResponse.StatusCode); // Add a file to local repo and try to push with designer await File.WriteAllTextAsync($"{CreatedFolderPath}/fileAlreadyInRepository.txt", "I am a new file from studio."); using var commitAndPushContent = new StringContent(GetCommitInfoJson("test commit", org, targetRepo), Encoding.UTF8, MediaTypeNames.Application.Json); using HttpResponseMessage commitAndPushResponse = await HttpClient.PostAsync($"designer/api/repos/repo/{org}/{targetRepo}/commit-and-push", commitAndPushContent); - commitAndPushResponse.StatusCode.Should().Be(HttpStatusCode.Conflict); + Assert.Equal(HttpStatusCode.Conflict, commitAndPushResponse.StatusCode); } } } diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs index 88b75a6ae6c..0ab890d84d6 100644 --- a/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs +++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs @@ -4,11 +4,9 @@ using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; -using Altinn.Studio.Designer.Models.Dto; using Altinn.Studio.Designer.RepositoryClient.Model; using Designer.Tests.Fixtures; using Designer.Tests.Utils; -using FluentAssertions; using Xunit; namespace Designer.Tests.GiteaIntegrationTests @@ -30,14 +28,14 @@ public async Task GetCurrentUser_ShouldReturnOk(string expectedUserName, string using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - response.StatusCode.Should().Be(HttpStatusCode.OK); - response.Headers.First(h => h.Key == "Set-Cookie").Value.Should().Contain(e => e.Contains("XSRF-TOKEN")); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains("XSRF-TOKEN", response.Headers.GetValues("Set-Cookie").First()); string content = await response.Content.ReadAsStringAsync(); var user = JsonSerializer.Deserialize<User>(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - user.Login.Should().Be(expectedUserName); - user.Email.Should().Be(expectedEmail); + Assert.Equal(expectedUserName, user.Login); + Assert.Equal(expectedEmail, user.Email); } [Theory] @@ -49,10 +47,11 @@ public async Task UserRepos_ShouldReturnOk(string org) string requestUrl = "designer/api/user/repos"; using var response = await HttpClient.GetAsync(requestUrl); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); var content = await response.Content.ReadAsAsync<List<Repository>>(); - content.Should().NotBeNull(); - content.Should().Contain(r => r.Name == targetRepo); + + Assert.NotNull(content); + Assert.Contains(content, r => r.Name == targetRepo); } [Theory] @@ -64,12 +63,12 @@ public async Task StarredEndpoints_ShouldBehaveAsExpected(string org) using var putStarredResponse = await HttpClient.PutAsync($"designer/api/user/starred/{org}/{targetRepo}", null); - putStarredResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, putStarredResponse.StatusCode); await GetAndVerifyStarredRepos(targetRepo); using var deleteStarredResponse = await HttpClient.DeleteAsync($"designer/api/user/starred/{org}/{targetRepo}"); - deleteStarredResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); + Assert.Equal(HttpStatusCode.NoContent, deleteStarredResponse.StatusCode); await GetAndVerifyStarredRepos(); } @@ -83,7 +82,7 @@ public async Task HasAccessToCreateRepository_ShouldReturnCorrectPermissions(str using var response = await HttpClient.GetAsync(requestUrl); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); string content = await response.Content.ReadAsStringAsync(); var deserializeOptions = new JsonSerializerOptions { @@ -92,19 +91,20 @@ public async Task HasAccessToCreateRepository_ShouldReturnCorrectPermissions(str var userOrgPermission = JsonSerializer.Deserialize<Team>(content, deserializeOptions); - userOrgPermission.Should().NotBeNull(); - userOrgPermission.CanCreateOrgRepo.Should().Be(expectedCanCreate); + Assert.NotNull(userOrgPermission); + Assert.Equal(expectedCanCreate, userOrgPermission.CanCreateOrgRepo); } private async Task GetAndVerifyStarredRepos(params string[] expectedStarredRepos) { using var response = await HttpClient.GetAsync("designer/api/user/starred"); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); var content = await response.Content.ReadAsAsync<List<Repository>>(); - content.Should().NotBeNull().And.HaveCount(expectedStarredRepos.Length); + Assert.NotNull(content); + Assert.Equal(expectedStarredRepos.Length, content.Count); foreach (string expectedStarredRepo in expectedStarredRepos) { - content.Should().Contain(r => r.Name == expectedStarredRepo); + Assert.Contains(content, r => r.Name == expectedStarredRepo); } } } diff --git a/backend/tests/Designer.Tests/Helpers/AltinnStudioRepositoryScannerTests.cs b/backend/tests/Designer.Tests/Helpers/AltinnStudioRepositoryScannerTests.cs index 373acdd8df7..f6e5486c5b3 100644 --- a/backend/tests/Designer.Tests/Helpers/AltinnStudioRepositoryScannerTests.cs +++ b/backend/tests/Designer.Tests/Helpers/AltinnStudioRepositoryScannerTests.cs @@ -1,6 +1,5 @@ using System.IO; using Altinn.Studio.Designer.Helpers; -using FluentAssertions; using Xunit; namespace Designer.Tests.Helpers; @@ -11,6 +10,6 @@ public class AltinnStudioRepositoryScannerTests public void ShouldBeAbleToFindRootDirectoryOfRepository() { string actual = AltinnStudioRepositoryScanner.FindRootDotEnvFilePath(); - actual.Replace(Path.DirectorySeparatorChar, '/').Should().EndWith("altinn-studio/.env"); + Assert.EndsWith("altinn-studio/.env", actual.Replace(Path.DirectorySeparatorChar, '/')); } } diff --git a/backend/tests/Designer.Tests/Helpers/PackageVersionHelperTests.cs b/backend/tests/Designer.Tests/Helpers/PackageVersionHelperTests.cs index 8c01f271c42..8260692ec42 100644 --- a/backend/tests/Designer.Tests/Helpers/PackageVersionHelperTests.cs +++ b/backend/tests/Designer.Tests/Helpers/PackageVersionHelperTests.cs @@ -2,7 +2,6 @@ using System.Linq; using Altinn.Studio.Designer.Helpers; using DotNet.Testcontainers.Builders; -using FluentAssertions; using Xunit; namespace Designer.Tests.Helpers @@ -22,11 +21,11 @@ public void TryGetPackageVersionFromCsprojFile_GivenValidCsprojFile_ReturnsTrue( { bool result = PackageVersionHelper.TryGetPackageVersionFromCsprojFile(testTemplateCsProjPath, input, out var version); - result.Should().Be(expectedResult); + Assert.Equal(expectedResult, result); if (result) { - version.ToString().Should().Be(expectedVersion); + Assert.Equal(expectedVersion, version.ToString()); } } } diff --git a/backend/tests/Designer.Tests/Hubs/SyncHub/SyncHubConnectionTests.cs b/backend/tests/Designer.Tests/Hubs/SyncHub/SyncHubConnectionTests.cs index 941f58bc90c..670c77e2772 100644 --- a/backend/tests/Designer.Tests/Hubs/SyncHub/SyncHubConnectionTests.cs +++ b/backend/tests/Designer.Tests/Hubs/SyncHub/SyncHubConnectionTests.cs @@ -2,7 +2,6 @@ using System.Reflection; using System.Threading.Tasks; using Designer.Tests.Controllers.ApiTests; -using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.SignalR.Client; using Xunit; @@ -22,10 +21,11 @@ public async Task Connect_Disconnect_Should_WorkAsExpected() { await When.ConnectionStarted(); - Then.HubConnection.State.Should().Be(HubConnectionState.Connected); + Assert.True(HubConnection.State == HubConnectionState.Connected); await And.When.HubConnection.StopAsync(); - Then.HubConnection.State.Should().Be(HubConnectionState.Disconnected); + + Assert.True(HubConnection.State == HubConnectionState.Disconnected); } private async Task ConnectionStarted() diff --git a/backend/tests/Designer.Tests/Infrastructure/GitRepository/AltinnAppGitRepositoryTests.cs b/backend/tests/Designer.Tests/Infrastructure/GitRepository/AltinnAppGitRepositoryTests.cs index ad920f48a16..7dd135940d3 100644 --- a/backend/tests/Designer.Tests/Infrastructure/GitRepository/AltinnAppGitRepositoryTests.cs +++ b/backend/tests/Designer.Tests/Infrastructure/GitRepository/AltinnAppGitRepositoryTests.cs @@ -9,7 +9,6 @@ using Altinn.Studio.Designer.Models; using Altinn.Studio.Designer.Models.App; using Designer.Tests.Utils; -using FluentAssertions; using Xunit; namespace Designer.Tests.Infrastructure.GitRepository @@ -43,36 +42,38 @@ public async Task GetApplicationMetadata_FileExists_ShouldHaveCorrectValues() ApplicationMetadata applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(); - applicationMetadata.Id.Should().Be("yabbin/hvem-er-hvem"); - applicationMetadata.Org.Should().Be("yabbin"); - applicationMetadata.Title.Should().ContainValues("Hvem er hvem?", "who-is-who"); - applicationMetadata.Title.Should().ContainKeys("nb", "en"); + Assert.Equal("yabbin/hvem-er-hvem", applicationMetadata.Id); + Assert.Equal("yabbin", applicationMetadata.Org); + Assert.Contains("Hvem er hvem?", applicationMetadata.Title.Values); + Assert.Contains("who-is-who", applicationMetadata.Title.Values); + Assert.Contains("nb", applicationMetadata.Title.Keys); + Assert.Contains("en", applicationMetadata.Title.Keys); - applicationMetadata.DataTypes.Should().HaveCount(2); - applicationMetadata.DataTypes.First(d => d.Id == "ref-data-as-pdf").AllowedContentTypes.First().Should().Be("application/pdf"); + Assert.Equal(2, applicationMetadata.DataTypes.Count); + Assert.Equal("application/pdf", applicationMetadata.DataTypes.First(d => d.Id == "ref-data-as-pdf").AllowedContentTypes.First()); DataType mainDataType = applicationMetadata.DataTypes.First(d => d.Id == "Kursdomene_HvemErHvem_M_2021-04-08_5742_34627_SERES"); - mainDataType.AllowedContentTypes.First().Should().Be("application/xml"); - mainDataType.AppLogic.ClassRef.Should().Be("Altinn.App.Models.HvemErHvem_M"); - mainDataType.AppLogic.AutoCreate.Should().BeTrue(); - mainDataType.MinCount.Should().Be(1); - mainDataType.MaxCount.Should().Be(1); - mainDataType.TaskId.Should().Be("Task_1"); - - applicationMetadata.PartyTypesAllowed.Person.Should().BeFalse(); - applicationMetadata.PartyTypesAllowed.Organisation.Should().BeFalse(); - applicationMetadata.PartyTypesAllowed.SubUnit.Should().BeFalse(); - applicationMetadata.PartyTypesAllowed.BankruptcyEstate.Should().BeFalse(); + Assert.Equal("application/xml", mainDataType.AllowedContentTypes.First()); + Assert.Equal("Altinn.App.Models.HvemErHvem_M", mainDataType.AppLogic.ClassRef); + Assert.True(mainDataType.AppLogic.AutoCreate); + Assert.Equal(1, mainDataType.MinCount); + Assert.Equal(1, mainDataType.MaxCount); + Assert.Equal("Task_1", mainDataType.TaskId); + + Assert.False(applicationMetadata.PartyTypesAllowed.Person); + Assert.False(applicationMetadata.PartyTypesAllowed.Organisation); + Assert.False(applicationMetadata.PartyTypesAllowed.SubUnit); + Assert.False(applicationMetadata.PartyTypesAllowed.BankruptcyEstate); DataField dataField = applicationMetadata.DataFields.First(d => d.Id == "GeekType"); - dataField.Path.Should().Be("InnrapporterteData.geekType"); - dataField.DataTypeId.Should().Be("Kursdomene_HvemErHvem_M_2021-04-08_5742_34627_SERES"); - - applicationMetadata.AutoDeleteOnProcessEnd.Should().BeFalse(); - applicationMetadata.Created.Should().BeSameDateAs(DateTime.Parse("2021-04-08T17:42:09.0883842Z")); - applicationMetadata.CreatedBy.Should().Be("Ronny"); - applicationMetadata.LastChanged.Should().BeSameDateAs(DateTime.Parse("2021-04-08T17:42:09.08847Z")); - applicationMetadata.LastChangedBy.Should().Be("Ronny"); + Assert.Equal("InnrapporterteData.geekType", dataField.Path); + Assert.Equal("Kursdomene_HvemErHvem_M_2021-04-08_5742_34627_SERES", dataField.DataTypeId); + + Assert.False(applicationMetadata.AutoDeleteOnProcessEnd); + Assert.Equal(DateTime.Parse("2021-04-08T17:42:09.0883842Z"), applicationMetadata.Created); + Assert.Equal("Ronny", applicationMetadata.CreatedBy); + Assert.Equal(DateTime.Parse("2021-04-08T17:42:09.08847Z"), applicationMetadata.LastChanged); + Assert.Equal("Ronny", applicationMetadata.LastChangedBy); } [Fact] @@ -85,8 +86,8 @@ public async Task GetTextResources_ResourceExists_ShouldReturn() var textResource = await altinnAppGitRepository.GetTextV1("nb"); - textResource.Should().NotBeNull(); - textResource.Resources.First(r => r.Id == "ServiceName").Value.Should().Be("Hvem er hvem?"); + Assert.NotNull(textResource); + Assert.Equal("Hvem er hvem?", textResource.Resources.First(r => r.Id == "ServiceName").Value); } [Fact] @@ -99,8 +100,10 @@ public void GetLanguages_NotOnlyResourceFilesInTextsFolder_ShouldReturnCorrectLa var languages = altinnAppGitRepository.GetLanguages(); - languages.Should().NotBeNull(); - languages.ToArray().Should().Equal("en", "nb"); + Assert.NotNull(languages); + Assert.Equal(2, languages.Count()); + Assert.Equal("en", languages.First()); + Assert.Equal("nb", languages.Last()); } [Fact] @@ -113,8 +116,8 @@ public void GetLayoutSetNames_WithAppThatUsesLayoutSet_ShouldReturnLayoutSetName string[] layoutSetNames = altinnAppGitRepository.GetLayoutSetNames(); - layoutSetNames.Should().NotBeNull(); - layoutSetNames.Should().HaveCount(2); + Assert.NotNull(layoutSetNames); + Assert.Equal(2, layoutSetNames.Length); } [Fact] @@ -127,8 +130,8 @@ public void GetLayoutSetNames_WithAppThatNotUsesLayoutSet_ShouldReturnDefaultLay string[] layoutSetNames = altinnAppGitRepository.GetLayoutSetNames(); - layoutSetNames.Should().NotBeNull(); - layoutSetNames.Should().HaveCount(1); + Assert.NotNull(layoutSetNames); + Assert.Single(layoutSetNames); } [Fact] @@ -141,7 +144,7 @@ public Task CheckIfAppUsesLayoutSets_ShouldReturnTrue() bool appUsesLayoutSets = altinnAppGitRepository.AppUsesLayoutSets(); - appUsesLayoutSets.Should().BeTrue(); + Assert.True(appUsesLayoutSets); return Task.CompletedTask; } @@ -155,7 +158,7 @@ public Task CheckIfAppUsesLayoutSets_ShouldReturnFalse() bool appUsesLayoutSets = altinnAppGitRepository.AppUsesLayoutSets(); - appUsesLayoutSets.Should().BeFalse(); + Assert.False(appUsesLayoutSets); return Task.CompletedTask; } @@ -170,8 +173,8 @@ public Task GetLayoutNames_WithAppThatUsesLayoutSet_ShouldReturnLayoutPathNames( string[] layoutNames = altinnAppGitRepository.GetLayoutNames(layoutSetName); - layoutNames.Should().NotBeNull(); - layoutNames.Should().HaveCount(2); + Assert.NotNull(layoutSetName); + Assert.Equal(2, layoutNames.Length); return Task.CompletedTask; } @@ -185,8 +188,8 @@ public Task GetLayoutNames_WithAppThatNotUsesLayoutSet_ShouldReturnLayoutPathNam string[] layoutNames = altinnAppGitRepository.GetLayoutNames(null); - layoutNames.Should().NotBeNull(); - layoutNames.Should().HaveCount(2); + Assert.NotNull(layoutNames); + Assert.Equal(2, layoutNames.Length); return Task.CompletedTask; } @@ -202,8 +205,9 @@ public async Task GetLayout_WithAppThatUsesLayoutSet_ShouldReturnLayout() JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName, layoutName); - formLayout.Should().NotBeNull(); - formLayout["data"]["layout"].Should().NotBeNull(); + Assert.NotNull(formLayout); + Assert.NotNull(formLayout["data"]); + Assert.NotNull(formLayout["data"]["layout"]); } [Fact] @@ -217,8 +221,9 @@ public async Task GetLayout_WithAppThatNotUsesLayoutSet_ShouldReturnLayout() JsonNode formLayout = await altinnAppGitRepository.GetLayout(null, layoutName); - formLayout.Should().NotBeNull(); - formLayout["data"]["layout"].Should().NotBeNull(); + Assert.NotNull(formLayout); + Assert.NotNull(formLayout["data"]); + Assert.NotNull(formLayout["data"]["layout"]); } [Fact] @@ -240,9 +245,10 @@ public async Task SaveLayout_ShouldUpdateLayoutInApp() await altinnAppGitRepository.SaveLayout(layoutSetName, layoutName, formLayoutToSave); JsonNode formLayoutSaved = await altinnAppGitRepository.GetLayout(layoutSetName, layoutName); - formLayoutSaved.Should().NotBeNull(); - formLayoutSaved["data"]["layout"].Should().NotBeNull(); - (formLayoutSaved["data"]["layout"] as JsonArray).Should().HaveCount(1); + Assert.NotNull(formLayoutSaved); + Assert.NotNull(formLayoutSaved["data"]); + Assert.NotNull(formLayoutSaved["data"]["layout"]); + Assert.Single(formLayoutSaved["data"]["layout"] as JsonArray); } finally { @@ -261,11 +267,12 @@ public async Task GetOptions_WithAppThatHasOptions_ShouldReturnSpecificOptionsLi AltinnAppGitRepository altinnAppGitRepository = PrepareRepositoryForTest(org, repository, developer); string options = await altinnAppGitRepository.GetOptionsList(optionsId); - options.Should().NotBeNull(); + + Assert.NotNull(options); var optionsArray = JsonNode.Parse(options).AsArray(); - optionsArray.Count.Should().Be(2); - optionsArray[0]["label"].ToString().Should().Be("label1"); - optionsArray[1]["label"].ToString().Should().Be("label2"); + Assert.Equal(2, optionsArray.Count); + Assert.Equal("label1", optionsArray[0]["label"].ToString()); + Assert.Equal("label2", optionsArray[1]["label"].ToString()); } [Fact] @@ -290,20 +297,20 @@ public Task GetOptionListIds_WithAppThatHasOptionLists_ShouldReturnOptionListPat string[] optionListIds = altinnAppGitRepository.GetOptionsListIds(); - optionListIds.Should().NotBeNull(); - optionListIds.Should().HaveCount(3); + Assert.NotNull(optionListIds); + Assert.Equal(3, optionListIds.Length); return Task.CompletedTask; } [Fact] - public Task GetOptionListIds_WithAppThatHasNoOptionLists_ShouldThrowNotFoundException() + public void GetOptionListIds_WithAppThatHasNoOptionLists_ShouldReturnEmptyList() { string org = "ttd"; string repository = "empty-app"; string developer = "testUser"; AltinnAppGitRepository altinnAppGitRepository = PrepareRepositoryForTest(org, repository, developer); - Assert.Throws<LibGit2Sharp.NotFoundException>(altinnAppGitRepository.GetOptionsListIds); - return Task.CompletedTask; + var optionsIds = altinnAppGitRepository.GetOptionsListIds(); + Assert.Equal([], optionsIds); } [Fact] diff --git a/backend/tests/Designer.Tests/Mocks/IGiteaMock.cs b/backend/tests/Designer.Tests/Mocks/IGiteaMock.cs index 37094770ba9..f717e8efbda 100644 --- a/backend/tests/Designer.Tests/Mocks/IGiteaMock.cs +++ b/backend/tests/Designer.Tests/Mocks/IGiteaMock.cs @@ -9,6 +9,7 @@ using Altinn.Studio.Designer.Services.Interfaces; using Designer.Tests.Utils; +using Organization = Altinn.Studio.Designer.RepositoryClient.Model.Organization; namespace Designer.Tests.Mocks { @@ -131,7 +132,13 @@ public Task<List<Team>> GetTeams() public Task<List<Organization>> GetUserOrganizations() { - throw new NotImplementedException(); + var organizations = new List<Organization> + { + new Organization { Username = "Org1", Id = 1 }, // Example items + new Organization { Username = "Org2", Id = 2 } + }; + + return Task.FromResult(organizations); } public Task<IList<Repository>> GetUserRepos() diff --git a/backend/tests/Designer.Tests/Models/AltinnCoreFileTests.cs b/backend/tests/Designer.Tests/Models/AltinnCoreFileTests.cs index cd0b987d342..e69a6b57b9d 100644 --- a/backend/tests/Designer.Tests/Models/AltinnCoreFileTests.cs +++ b/backend/tests/Designer.Tests/Models/AltinnCoreFileTests.cs @@ -2,7 +2,6 @@ using System.IO; using Altinn.Studio.Designer.Models; using Designer.Tests.Utils; -using FluentAssertions; using Xunit; namespace Designer.Tests @@ -27,7 +26,7 @@ public void CreateFromPath_ValidPath_ShouldCreateInstanse() Assert.Equal(@"/App/models/0678.xsd", altinnCoreFile.RepositoryRelativeUrl); Assert.Equal(directory, altinnCoreFile.Directory); Assert.Equal(filePath, altinnCoreFile.FilePath); - altinnCoreFile.LastChanged.Should().BeOnOrBefore(DateTime.Now); + Assert.True(altinnCoreFile.LastChanged < DateTime.Now); } [Theory] diff --git a/backend/tests/Designer.Tests/Services/AppDevelopmentServiceTest.cs b/backend/tests/Designer.Tests/Services/AppDevelopmentServiceTest.cs index 629ec88fd7d..44688b3eac8 100644 --- a/backend/tests/Designer.Tests/Services/AppDevelopmentServiceTest.cs +++ b/backend/tests/Designer.Tests/Services/AppDevelopmentServiceTest.cs @@ -10,7 +10,6 @@ using Altinn.Studio.Designer.Services.Implementation; using Altinn.Studio.Designer.Services.Interfaces; using Designer.Tests.Utils; -using FluentAssertions; using Moq; using Xunit; @@ -45,7 +44,7 @@ public async Task GetLayoutSettings_FromAppWithOutLayoutSet_ShouldReturnSettings CreatedTestRepoPath = await TestDataHelper.CopyRepositoryForTest(_org, repository, _developer, targetRepository); var layoutSettings = await _appDevelopmentService.GetLayoutSettings(AltinnRepoEditingContext.FromOrgRepoDeveloper(_org, targetRepository, _developer), null); - layoutSettings.Should().NotBeNull(); + Assert.NotNull(layoutSettings); } [Fact] @@ -73,8 +72,9 @@ public async Task SaveLayoutSettingsWithAdditionalPage_ToAppWithOutLayoutSet_Sho await _appDevelopmentService.SaveLayoutSettings(AltinnRepoEditingContext.FromOrgRepoDeveloper(_org, targetRepository, _developer), layoutSettingsUpdated, layoutSetName); var layoutSettings = await _appDevelopmentService.GetLayoutSettings(AltinnRepoEditingContext.FromOrgRepoDeveloper(_org, targetRepository, _developer), layoutSetName); - layoutSettings.Should().NotBeNull(); - (layoutSettings["pages"]["order"] as JsonArray).Should().HaveCount(3); + Assert.NotNull(layoutSettings); + + Assert.Equal(3, ((JsonArray)layoutSettings!["pages"]!["order"]!).Count); } [Fact] @@ -86,7 +86,7 @@ public async Task GetLayoutSettings_FromAppWithLayoutSet_ShouldReturnSettings() CreatedTestRepoPath = await TestDataHelper.CopyRepositoryForTest(_org, _repository, _developer, targetRepository); var layoutSettings = await _appDevelopmentService.GetLayoutSettings(AltinnRepoEditingContext.FromOrgRepoDeveloper(_org, targetRepository, _developer), layoutSetName); - layoutSettings.Should().NotBeNull(); + Assert.NotNull(layoutSettings); } @@ -99,8 +99,9 @@ public async Task GetLayoutSettings_FromAppWithLayoutSetButNoSettingsExist_Shoul CreatedTestRepoPath = await TestDataHelper.CopyRepositoryForTest(_org, _repository, _developer, targetRepository); var layoutSettings = await _appDevelopmentService.GetLayoutSettings(AltinnRepoEditingContext.FromOrgRepoDeveloper(_org, targetRepository, _developer), layoutSetName); - layoutSettings.Should().NotBeNull(); - (layoutSettings["pages"]["order"] as JsonArray).Should().HaveCount(2); + Assert.NotNull(layoutSettings); + + Assert.Equal(2, ((JsonArray)layoutSettings!["pages"]!["order"]!).Count); } [Fact] @@ -139,9 +140,10 @@ public async Task UpdateLayoutSet_WhenLayoutSetExistsAndNewIdIsProvided_ShouldUp List<string> layoutSetFileNamesAfterUpdate = GetFileNamesInLayoutSet(newLayoutSetName); // Assert - updatedLayoutSets.Sets.Should().HaveCount(4); - updatedLayoutSets.Sets.Find(set => set.Id == newLayoutSetName).Should().NotBeNull(); - layoutSetFileNamesBeforeUpdate.Should().BeEquivalentTo(layoutSetFileNamesAfterUpdate); + Assert.Equal(4, updatedLayoutSets.Sets.Count); + Assert.NotNull(updatedLayoutSets.Sets.Find(set => set.Id == newLayoutSetName)); + Assert.Equal(layoutSetFileNamesBeforeUpdate.Count, layoutSetFileNamesAfterUpdate.Count); + } [Fact] @@ -157,9 +159,9 @@ public async Task AddLayoutSet_WhenLayoutSetDoesNotExist_ShouldAddNewLayoutSet() var updatedLayoutSets = await _appDevelopmentService.AddLayoutSet(AltinnRepoEditingContext.FromOrgRepoDeveloper(_org, targetRepository, _developer), newLayoutSet); // Assert - updatedLayoutSets.Should().NotBeNull(); - updatedLayoutSets.Sets.Should().HaveCount(5); - updatedLayoutSets.Sets.Should().Contain(newLayoutSet); + Assert.NotNull(updatedLayoutSets); + Assert.Equal(5, updatedLayoutSets.Sets.Count); + Assert.Contains(newLayoutSet, updatedLayoutSets.Sets); } [Fact] @@ -191,7 +193,7 @@ public async Task UpdateLayoutSet_WhenAppHasNoLayoutSets_ShouldThrowFileNotFound Func<Task> act = async () => await _appDevelopmentService.UpdateLayoutSetName(AltinnRepoEditingContext.FromOrgRepoDeveloper(_org, targetRepository, _developer), "layoutSet1", "someName"); // Assert - await act.Should().ThrowAsync<NoLayoutSetsFileFoundException>(); + await Assert.ThrowsAsync<NoLayoutSetsFileFoundException>(act); } [Fact] @@ -207,7 +209,7 @@ public async Task AddLayoutSet_WhenAppHasNoLayoutSets_ShouldThrowFileNotFoundExc Func<Task> act = async () => await _appDevelopmentService.AddLayoutSet(AltinnRepoEditingContext.FromOrgRepoDeveloper(_org, targetRepository, _developer), new() { Id = "layoutSet1" }); // Assert - await act.Should().ThrowAsync<NoLayoutSetsFileFoundException>(); + await Assert.ThrowsAsync<NoLayoutSetsFileFoundException>(act); } private List<string> GetFileNamesInLayoutSet(string layoutSetName) diff --git a/backend/tests/Designer.Tests/Services/ProcessModelingServiceTests.cs b/backend/tests/Designer.Tests/Services/ProcessModelingServiceTests.cs index 9762c986c34..0607c5f620b 100644 --- a/backend/tests/Designer.Tests/Services/ProcessModelingServiceTests.cs +++ b/backend/tests/Designer.Tests/Services/ProcessModelingServiceTests.cs @@ -7,7 +7,6 @@ using Altinn.Studio.Designer.Services.Implementation.ProcessModeling; using Altinn.Studio.Designer.Services.Interfaces; using Designer.Tests.Utils; -using FluentAssertions; using Moq; using NuGet.Versioning; using SharedResources.Tests; @@ -38,11 +37,11 @@ public void GetProcessDefinitionTemplates_GivenVersion_ReturnsListOfTemplates(st var result = processModelingService.GetProcessDefinitionTemplates(version).ToList(); - result.Count.Should().Be(expectedTemplates.Length); + Assert.Equal(expectedTemplates.Length, result.Count); foreach (string expectedTemplate in expectedTemplates) { - result.Should().Contain(expectedTemplate); + Assert.Contains(expectedTemplate, result); } } @@ -60,7 +59,7 @@ public async Task GetTaskTypeFromProcessDefinition_GivenProcessDefinition_Return string taskType = await processModelingService.GetTaskTypeFromProcessDefinition(AltinnRepoEditingContext.FromOrgRepoDeveloper(org, targetRepository, developer), "layoutSet1"); // Assert - taskType.Should().Be("data"); + Assert.Equal("data", taskType); } public static IEnumerable<object[]> TemplatesTestData => new List<object[]> diff --git a/backend/tests/Designer.Tests/Services/RepositorySITests.cs b/backend/tests/Designer.Tests/Services/RepositorySITests.cs index 9088a6dd9d4..f8edd9c6b94 100644 --- a/backend/tests/Designer.Tests/Services/RepositorySITests.cs +++ b/backend/tests/Designer.Tests/Services/RepositorySITests.cs @@ -19,7 +19,6 @@ using Designer.Tests.Mocks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; @@ -122,7 +121,7 @@ public async Task CreateRepository_DoesNotExists_ShouldCreate() { await repositoryService.CreateService(org, new ServiceConfiguration() { RepositoryName = repositoryName, ServiceName = repositoryName }); var altinnStudioSettings = await new AltinnGitRepositoryFactory(repositoriesRootDirectory).GetAltinnGitRepository(org, repositoryName, developer).GetAltinnStudioSettings(); - altinnStudioSettings.RepoType.Should().Be(AltinnRepositoryType.App); + Assert.Equal(AltinnRepositoryType.App, altinnStudioSettings.RepoType); } finally { diff --git a/backend/tests/Designer.Tests/Services/SchemaModelServiceTests.cs b/backend/tests/Designer.Tests/Services/SchemaModelServiceTests.cs index 344f341d896..06c08163890 100644 --- a/backend/tests/Designer.Tests/Services/SchemaModelServiceTests.cs +++ b/backend/tests/Designer.Tests/Services/SchemaModelServiceTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; @@ -14,7 +13,6 @@ using Altinn.Studio.Designer.Services.Implementation; using Altinn.Studio.Designer.Services.Interfaces; using Designer.Tests.Utils; -using FluentAssertions; using Moq; using SharedResources.Tests; using Xunit; @@ -48,11 +46,11 @@ public async Task DeleteSchema_AppRepo_ShouldDelete() try { var schemaFiles = _schemaModelService.GetSchemaFiles(editingContext); - schemaFiles.Should().HaveCount(7); + Assert.Equal(7, schemaFiles.Count); var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); var applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(); - applicationMetadata.DataTypes.Should().HaveCount(2); + Assert.Equal(2, applicationMetadata.DataTypes.Count); // Act var schemaToDelete = schemaFiles.First(s => s.FileName == "Kursdomene_HvemErHvem_M_2021-04-08_5742_34627_SERES.schema.json"); @@ -60,9 +58,9 @@ public async Task DeleteSchema_AppRepo_ShouldDelete() // Assert schemaFiles = _schemaModelService.GetSchemaFiles(editingContext); - schemaFiles.Should().HaveCount(6); + Assert.Equal(6, schemaFiles.Count); applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(); - applicationMetadata.DataTypes.Should().HaveCount(1); + Assert.Single(applicationMetadata.DataTypes); } finally { @@ -86,7 +84,7 @@ public async Task DeleteSchema_AppRepoWithLayoutSets_ShouldDelete() string dataModelName = "datamodel"; var schemaFiles = _schemaModelService.GetSchemaFiles(editingContext); - schemaFiles.Should().HaveCount(1); + Assert.Single(schemaFiles); var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); var applicationMetadataBefore = await altinnAppGitRepository.GetApplicationMetadata(); @@ -98,12 +96,13 @@ public async Task DeleteSchema_AppRepoWithLayoutSets_ShouldDelete() // Assert schemaFiles = _schemaModelService.GetSchemaFiles(editingContext); - schemaFiles.Should().HaveCount(0); + Assert.Empty(schemaFiles); var applicationMetadataAfter = await altinnAppGitRepository.GetApplicationMetadata(); - applicationMetadataAfter.DataTypes.Should().HaveCount(applicationMetadataBefore.DataTypes.Count - 1); + Assert.Equal(applicationMetadataBefore.DataTypes.Count - 1, applicationMetadataAfter.DataTypes.Count); var layoutSetsAfter = await altinnAppGitRepository.GetLayoutSetsFile(); - layoutSetsBefore.Sets.Exists(set => set.DataType == dataModelName).Should().BeTrue(); - layoutSetsAfter.Sets.Exists(set => set.DataType == dataModelName).Should().BeFalse(); + + Assert.True(layoutSetsBefore.Sets.Exists(set => set.DataType == dataModelName)); + Assert.False(layoutSetsAfter.Sets.Exists(set => set.DataType == dataModelName)); } finally { @@ -126,7 +125,7 @@ public async Task DeleteSchema_ModelsRepo_ShouldDelete() { var schemaFiles = _schemaModelService.GetSchemaFiles(editingContext); - schemaFiles.Should().HaveCount(6); + Assert.Equal(6, schemaFiles.Count); // Act var schemaToDelete = schemaFiles.First(s => s.FileName == "Kursdomene_HvemErHvem_M_2021-04-08_5742_34627_SERES.schema.json"); @@ -134,7 +133,7 @@ public async Task DeleteSchema_ModelsRepo_ShouldDelete() // Assert schemaFiles = _schemaModelService.GetSchemaFiles(editingContext); - schemaFiles.Should().HaveCount(5); + Assert.Equal(5, schemaFiles.Count); } finally { @@ -165,7 +164,7 @@ public async Task UpdateSchema_AppRepo_ShouldUpdate() var updatedSchema = await altinnGitRepository.ReadTextByRelativePathAsync("App/models/HvemErHvem_SERES.schema.json"); string serializedExpectedSchemaUpdates = FormatJsonString(updatedSchema); - updatedSchema.Should().BeEquivalentTo(serializedExpectedSchemaUpdates); + Assert.Equal(serializedExpectedSchemaUpdates, updatedSchema); var xsd = await altinnGitRepository.ReadTextByRelativePathAsync("App/models/HvemErHvem_SERES.xsd"); @@ -180,8 +179,8 @@ public async Task UpdateSchema_AppRepo_ShouldUpdate() // </xsd:complexType> // </xsd:schema> var xsdSchema = XDocument.Parse(xsd); - xsdSchema.Root.Should().NotBeNull(); - xsdSchema.Root.Elements().First().Attributes().First(a => a.Name.LocalName == "name").Should().HaveValue("root"); + Assert.NotNull(xsdSchema.Root); + Assert.Equal("root", xsdSchema.Root.Elements().First().Attributes().First(a => a.Name.LocalName == "name").Value); } finally { @@ -236,7 +235,7 @@ public async Task UpdateSchema_NoModelMetadataForModelInRepo_ShouldOnlyUpdate() Assert.False(altinnGitRepository.FileExistsByRelativePath("App/models/HvemErHvem_SERES.metadata.json")); var updatedSchema = await altinnGitRepository.ReadTextByRelativePathAsync("App/models/HvemErHvem_SERES.schema.json"); string serializedExpectedSchemaUpdates = FormatJsonString(updatedSchema); - updatedSchema.Should().BeEquivalentTo(serializedExpectedSchemaUpdates); + Assert.Equal(serializedExpectedSchemaUpdates, updatedSchema); } finally { @@ -265,7 +264,7 @@ public async Task UpdateSchema_InvalidJsonSchema_ShouldThrowException() }); Assert.NotNull(exception.CustomErrorMessages); - exception.CustomErrorMessages.Should().ContainSingle(c => c.Contains("root': member names cannot be the same as their enclosing type")); + Assert.Single(exception.CustomErrorMessages, c => c.Contains("root': member names cannot be the same as their enclosing type")); } [Fact] @@ -288,7 +287,8 @@ public async Task UploadSchemaFromXsd_InvalidXsd_ThrowsException() Func<Task> action = () => _schemaModelService.BuildSchemaFromXsd(editingContext, fileName, xsdStream); // Act/assert - await action.Should().ThrowAsync<XmlSchemaException>(); + await Assert.ThrowsAsync<XmlSchemaException>(action); + } finally { @@ -322,9 +322,10 @@ public async Task UploadSchemaFromXsd_ValidNonSeresXsd_ModelsCreated() // Assert var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); - altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.metadata.json").Should().BeFalse(); - altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.schema.json").Should().BeTrue(); - altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.cs").Should().BeTrue(); + + Assert.False(altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.metadata.json")); + Assert.True(altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.schema.json")); + Assert.True(altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.cs")); } finally { @@ -358,10 +359,11 @@ public async Task UploadSchemaFromXsd_OED_ModelsCreated() // Assert var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); - altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.metadata.json").Should().BeFalse(); - altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.schema.json").Should().BeTrue(); - altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.xsd").Should().BeTrue(); - altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.cs").Should().BeTrue(); + + Assert.False(altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.metadata.json")); + Assert.True(altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.schema.json")); + Assert.True(altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.xsd")); + Assert.True(altinnAppGitRepository.FileExistsByRelativePath($"{relativeDirectory}/{schemaName}.cs")); } finally { diff --git a/backend/tests/Designer.Tests/Services/SourceControlLoggingDecoratorTests.cs b/backend/tests/Designer.Tests/Services/SourceControlLoggingDecoratorTests.cs index b16e0199bde..b3f8a59a87b 100644 --- a/backend/tests/Designer.Tests/Services/SourceControlLoggingDecoratorTests.cs +++ b/backend/tests/Designer.Tests/Services/SourceControlLoggingDecoratorTests.cs @@ -5,7 +5,6 @@ using Altinn.Studio.Designer.RepositoryClient.Model; using Altinn.Studio.Designer.Services.Implementation; using Altinn.Studio.Designer.Services.Interfaces; -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; @@ -38,7 +37,7 @@ public void Container_DecoratesISourceControlService_ReturnsDecoratorClass() var service = serviceProvider.GetService<ISourceControl>(); - service.Should().BeOfType<SourceControlLoggingDecorator>(); + Assert.IsType<SourceControlLoggingDecorator>(service); } [Fact] diff --git a/backend/tests/Designer.Tests/Services/TextsServiceTest.cs b/backend/tests/Designer.Tests/Services/TextsServiceTest.cs index 74e2764f98e..7b33b52e19f 100644 --- a/backend/tests/Designer.Tests/Services/TextsServiceTest.cs +++ b/backend/tests/Designer.Tests/Services/TextsServiceTest.cs @@ -11,7 +11,6 @@ using Altinn.Studio.Designer.TypedHttpClients.AltinnStorage; using Designer.Tests.Mocks; using Designer.Tests.Utils; -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; @@ -54,8 +53,8 @@ public async Task UpdateRelatedFiles_KeyExistInLayoutInLayoutSet_ShouldFindNewId AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - (formLayout["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString().Should().Be("new-id"); + Assert.NotNull(formLayout); + Assert.Equal("new-id", (formLayout["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString()); } [Fact] @@ -70,10 +69,10 @@ public async Task UpdateRelatedFiles_MultipleKeysExistInLayoutsAcrossLayoutSets_ JsonNode formLayout1 = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); JsonNode formLayout2 = await altinnAppGitRepository.GetLayout(layoutSetName2, layoutName2); - formLayout1.Should().NotBeNull(); - formLayout2.Should().NotBeNull(); - (formLayout1["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString().Should().Be("new-id"); - (formLayout2["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString().Should().Be("new-id"); + Assert.NotNull(formLayout1); + Assert.NotNull(formLayout2); + Assert.Equal("new-id", (formLayout1["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString()); + Assert.Equal("new-id", (formLayout2["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString()); } [Fact] @@ -86,8 +85,8 @@ public async Task UpdateRelatedFiles_KeyExistInLayout_ShouldFindNewId() AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - (formLayout["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString().Should().Be("new-id"); + Assert.NotNull(formLayout); + Assert.Equal("new-id", (formLayout["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString()); } [Fact] @@ -100,8 +99,8 @@ public async Task UpdateRelatedFiles_KeyDoesNotExistInLayout_ShouldReturn() AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - (formLayout["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString().Should().Be("some-old-id"); + Assert.NotNull(formLayout); + Assert.Equal("some-old-id", (formLayout["data"]["layout"] as JsonArray)[0]["textResourceBindings"]["title"].ToString()); } [Fact] @@ -114,8 +113,8 @@ public async Task UpdateRelatedFiles_WithoutNewKey_ShouldDeleteReference() AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - formLayout.ToString().Should().NotContain("some-old-key"); + Assert.NotNull(formLayout); + Assert.DoesNotContain("some-old-key", formLayout.ToString()); } [Fact] @@ -128,10 +127,10 @@ public async Task UpdateRelatedFiles_Options_ShouldUpdateLabel() AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["label"].ToString().Should().Be("new-id"); - (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["helpText"].ToString().Should().Be("help-text-used-by-options"); - (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["description"].ToString().Should().Be("description-used-by-options"); + Assert.NotNull(formLayout); + Assert.Equal("new-id", (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["label"].ToString()); + Assert.Equal("help-text-used-by-options", (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["helpText"].ToString()); + Assert.Equal("description-used-by-options", (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["description"].ToString()); } [Fact] @@ -144,10 +143,10 @@ public async Task UpdateRelatedFiles_Options_ShouldUpdateHelpTextAndDescription( AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["label"].ToString().Should().Be("id-used-by-options"); - (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["helpText"].ToString().Should().Be("new-id"); - (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["description"].ToString().Should().Be("new-id"); + Assert.NotNull(formLayout); + Assert.Equal("id-used-by-options", (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["label"].ToString()); + Assert.Equal("new-id", (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["helpText"].ToString()); + Assert.Equal("new-id", (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["description"].ToString()); } [Fact] @@ -160,8 +159,8 @@ public async Task UpdateRelatedFiles_Options_ShouldNotDeleteOptionLabel() AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["label"].ToString().Should().Be("id-used-by-options"); + Assert.NotNull(formLayout); + Assert.Equal("id-used-by-options", (formLayout["data"]["layout"] as JsonArray)[2]["options"][0]["label"].ToString()); } [Fact] @@ -175,9 +174,9 @@ public async Task UpdateRelatedFiles_Options_ShouldUpdateOptionsList() string raw = await altinnAppGitRepository.GetOptionsList("test-options"); JsonNode optionsList = JsonNode.Parse(raw); - optionsList.Should().NotBeNull(); - (optionsList as JsonArray)[0]["label"].ToString().Should().Be("label1new"); - (optionsList as JsonArray)[1]["label"].ToString().Should().Be("label2"); + Assert.NotNull(optionsList); + Assert.Equal("label1new", (optionsList as JsonArray)[0]["label"].ToString()); + Assert.Equal("label2", (optionsList as JsonArray)[1]["label"].ToString()); } [Fact] @@ -191,9 +190,9 @@ public async Task UpdateRelatedFiles_Options_ShouldNotDeleteFromOptionsList() string raw = await altinnAppGitRepository.GetOptionsList("test-options"); JsonNode optionsList = JsonNode.Parse(raw); - optionsList.Should().NotBeNull(); - (optionsList as JsonArray)[0]["label"].ToString().Should().Be("label1"); - (optionsList as JsonArray)[1]["label"].ToString().Should().Be("label2"); + Assert.NotNull(optionsList); + Assert.Equal("label1", (optionsList as JsonArray)[0]["label"].ToString()); + Assert.Equal("label2", (optionsList as JsonArray)[1]["label"].ToString()); } [Fact] @@ -206,8 +205,8 @@ public async Task UpdateRelatedFiles_Options_ShouldUpdateSourceLabel() AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - (formLayout["data"]["layout"] as JsonArray)[3]["source"]["label"].ToString().Should().Be("source-label-new"); + Assert.NotNull(formLayout); + Assert.Equal("source-label-new", (formLayout["data"]["layout"] as JsonArray)[3]["source"]["label"].ToString()); } [Fact] @@ -220,8 +219,8 @@ public async Task UpdateRelatedFiles_Options_ShouldNotDeleteSourceLabel() AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, targetRepository, developer); JsonNode formLayout = await altinnAppGitRepository.GetLayout(layoutSetName1, layoutName1); - formLayout.Should().NotBeNull(); - (formLayout["data"]["layout"] as JsonArray)[3]["source"]["label"].ToString().Should().Be("source-label"); + Assert.NotNull(formLayout); + Assert.Equal("source-label", (formLayout["data"]["layout"] as JsonArray)[3]["source"]["label"].ToString()); } public void Dispose() diff --git a/backend/tests/Designer.Tests/TypedHttpClients/DelegatingHandlers/CachingDelegatingHandlerTests.cs b/backend/tests/Designer.Tests/TypedHttpClients/DelegatingHandlers/CachingDelegatingHandlerTests.cs index 8db2081f3cc..b431d963b45 100644 --- a/backend/tests/Designer.Tests/TypedHttpClients/DelegatingHandlers/CachingDelegatingHandlerTests.cs +++ b/backend/tests/Designer.Tests/TypedHttpClients/DelegatingHandlers/CachingDelegatingHandlerTests.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Threading.Tasks; using Altinn.Studio.Designer.TypedHttpClients.DelegatingHandlers; -using FluentAssertions; using Microsoft.Extensions.Caching.Memory; using Xunit; @@ -37,10 +36,11 @@ public async Task Get_ShouldUseCache(byte[] expectedResponse) _memoryCache.Set($"{HttpMethod.Get}_http://nonexistingurl1234.no/", cacheResponseDataEntry); var response = await client.GetAsync("http://nonexistingurl1234.no/"); - response.StatusCode.Should().Be(HttpStatusCode.OK); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); byte[] responseArray = await response.Content.ReadAsByteArrayAsync(); - responseArray.Should().BeEquivalentTo(expectedResponse); + Assert.Equal(expectedResponse, responseArray); } [Fact] @@ -52,7 +52,7 @@ public async Task NonGetRequest_Keep_StatusCode() var response = await client.PostAsync("https://docs.altinn.studio/", null); - response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } [Fact] @@ -64,7 +64,7 @@ public async Task NonSuccessGetRequest_Keep_StatusCode() var response = await client.GetAsync("https://docs.altinn.studio/nonexisting"); - response.StatusCode.Should().Be(HttpStatusCode.NotFound); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] @@ -76,10 +76,10 @@ public async Task Get_ShouldBeCached() var response = await client.GetAsync("https://docs.altinn.studio/"); - response.IsSuccessStatusCode.Should().BeTrue(); - response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK); + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - _memoryCache.TryGetValue($"{HttpMethod.Get}_https://docs.altinn.studio/", out CachingDelegatingHandler.CacheResponseDataEntry _).Should().BeTrue(); + Assert.True(_memoryCache.TryGetValue($"{HttpMethod.Get}_https://docs.altinn.studio/", out CachingDelegatingHandler.CacheResponseDataEntry _)); } } } diff --git a/backend/tests/Designer.Tests/Utils/AssertionUtil.cs b/backend/tests/Designer.Tests/Utils/AssertionUtil.cs index 889b7d84f62..57fb3c0b1bd 100644 --- a/backend/tests/Designer.Tests/Utils/AssertionUtil.cs +++ b/backend/tests/Designer.Tests/Utils/AssertionUtil.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using Altinn.Authorization.ABAC.Xacml; using Altinn.Authorization.ABAC.Xacml.JsonProfile; using Microsoft.AspNetCore.Mvc; +using SharedResources.Tests; using Xunit; namespace Altinn.AccessManagement.Tests.Utils @@ -196,6 +198,28 @@ public static void AssertValidationProblemDetailsEqual(ValidationProblemDetails } } + public static void AssertEqualTo<TType>(TType expected, TType actual) + { + JsonSerializerOptions options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + Assert.True(JsonUtils.DeepEquals(JsonSerializer.Serialize(expected, options), + JsonSerializer.Serialize(actual, options))); + } + + public static void AssertCloseTo(DateTimeOffset expected, DateTimeOffset actual, TimeSpan tolerance) + { + TimeSpan difference = (expected - actual).Duration(); + Assert.True(difference <= tolerance); + } + + public static void AssertCloseTo(DateTime expected, DateTime actual, TimeSpan tolerance) + { + TimeSpan difference = (expected - actual).Duration(); + Assert.True(difference <= tolerance); + } + private static void AssertEqual(XacmlJsonResult expected, XacmlJsonResult actual) { Assert.Equal(expected.Decision, actual.Decision); diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/layouts/Side1.json new file mode 100644 index 00000000000..880092a7380 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/layouts/Side1.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "" + }, + "id": "Summary2-9iG1lB", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "" + }, + "id": "Summary2-R8HsuB", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-mL8NjJ", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-qWr0oa", + "taskId": "" + }, + "id": "Summary2-0BV88Q", + "type": "Summary2" + }, + { + "id": "NavigationButtons-DfcNol", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/layouts/Side2.json new file mode 100644 index 00000000000..1fc7b9c90e0 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component/layouts/Side2.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-qWr0oa", + "type": "Input" + }, + { + "id": "NavigationButtons-GAW8Dx", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/layouts/Side1.json new file mode 100644 index 00000000000..8489464261c --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/layouts/Side1.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-LCr3oK", + "type": "Input" + }, + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "" + }, + "id": "Summary2-eoT5QK", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "" + }, + "id": "Summary2-NJfE92", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-BGHvph", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-LCr3oK", + "taskId": "" + }, + "id": "Summary2-rb2ml2", + "type": "Summary2" + }, + { + "id": "NavigationButtons-t7UTGJ", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/layouts/Side2.json new file mode 100644 index 00000000000..5ebc0f7df8b --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/component2/layouts/Side2.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "component" + }, + "id": "Summary2-2FSqcf", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "component" + }, + "id": "Summary2-eGrDpP", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "component" + }, + "id": "Summary2-uJDgMu", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-qWr0oa", + "taskId": "component" + }, + "id": "Summary2-2JiT3P", + "type": "Summary2" + }, + { + "id": "NavigationButtons-BYcvaT", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout-sets.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout-sets.json new file mode 100644 index 00000000000..f916f692bec --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout-sets.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout-sets.schema.v1.json", + "sets": [ + { + "id": "component", + "tasks": [ + "component" + ] + }, + { + "id": "layout", + "tasks": [ + "layout" + ] + }, + { + "id": "component2", + "tasks": [ + "component2" + ] + }, + { + "id": "layout2", + "tasks": [ + "layout2" + ] + }, + { + "id": "layoutSet2", + "tasks": [ + "layoutSet2" + ] + } + ], + "uiSettings": {} +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout/Settings.json new file mode 100644 index 00000000000..b40e33a3211 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout/Settings.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout/layouts/Side1.json new file mode 100644 index 00000000000..2f467af518a --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout/layouts/Side1.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "" + }, + "id": "Summary2-FEI1HC", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "" + }, + "id": "Summary2-e2yYpk", + "type": "Summary2" + }, + { + "id": "NavigationButtons-7g3XcW", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/layouts/Side1.json new file mode 100644 index 00000000000..2530921cc4e --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/layouts/Side1.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-QCSonu", + "type": "Input" + }, + { + "target": { + "type": "layoutSet", + "id": "" + }, + "id": "Summary2-18hFaH", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1" + }, + "id": "Summary2-iZJ80j", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-V55C6Q", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-QCSonu" + }, + "id": "Summary2-aI91Tv", + "type": "Summary2" + }, + { + "id": "NavigationButtons-2hGPi1", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/layouts/Side2.json new file mode 100644 index 00000000000..edd72d878e9 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layout2/layouts/Side2.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "layout" + }, + "id": "Summary2-MfRiX8", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "layout" + }, + "id": "Summary2-66YavC", + "type": "Summary2" + }, + { + "id": "NavigationButtons-h0g3Wt", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/layouts/Side1.json new file mode 100644 index 00000000000..60fc1029c15 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/layouts/Side1.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-wrspcN", + "type": "Input" + }, + { + "target": { + "type": "layoutSet", + "id": "" + }, + "id": "Summary2-00SFBO", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1" + }, + "id": "Summary2-4069IB", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-Orxmu1", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-wrspcN", + "taskId": "" + }, + "id": "Summary2-2YoJGY", + "type": "Summary2" + }, + { + "id": "NavigationButtons-KnXA9y", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/layouts/Side2.json new file mode 100644 index 00000000000..ba9b66dff54 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components-after-deleting-references/App/ui/layoutSet2/layouts/Side2.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "id": "NavigationButtons-5ukC2N", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/layouts/Side1.json new file mode 100644 index 00000000000..112caa4c318 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/layouts/Side1.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "" + }, + "id": "Summary2-9iG1lB", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "" + }, + "id": "Summary2-R8HsuB", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-mL8NjJ", + "type": "Summary2", + "overrides": [ + { + "componentId": "Input-Om7N3y", + "displayType": "string" + } + ] + }, + { + "target": { + "type": "component", + "id": "Input-qWr0oa", + "taskId": "" + }, + "id": "Summary2-0BV88Q", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-Om7N3y", + "taskId": "" + }, + "id": "Summary2-dTepe0", + "type": "Summary2" + }, + { + "id": "NavigationButtons-DfcNol", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/layouts/Side2.json new file mode 100644 index 00000000000..3989fafc6be --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component/layouts/Side2.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-Om7N3y", + "type": "Input" + }, + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-qWr0oa", + "type": "Input" + }, + { + "id": "NavigationButtons-GAW8Dx", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/layouts/Side1.json new file mode 100644 index 00000000000..8489464261c --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/layouts/Side1.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-LCr3oK", + "type": "Input" + }, + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "" + }, + "id": "Summary2-eoT5QK", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "" + }, + "id": "Summary2-NJfE92", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-BGHvph", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-LCr3oK", + "taskId": "" + }, + "id": "Summary2-rb2ml2", + "type": "Summary2" + }, + { + "id": "NavigationButtons-t7UTGJ", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/layouts/Side2.json new file mode 100644 index 00000000000..b754c0caf49 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/component2/layouts/Side2.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "component" + }, + "id": "Summary2-2FSqcf", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "component" + }, + "id": "Summary2-eGrDpP", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "component" + }, + "id": "Summary2-uJDgMu", + "type": "Summary2", + "overrides": [ + { + "componentId": "Input-Om7N3y", + "displayType": "string" + } + ] + }, + { + "target": { + "type": "component", + "id": "Input-qWr0oa", + "taskId": "component" + }, + "id": "Summary2-2JiT3P", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-Om7N3y", + "taskId": "component" + }, + "id": "Summary2-vIXkWO", + "type": "Summary2" + }, + { + "id": "NavigationButtons-BYcvaT", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout-sets.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout-sets.json new file mode 100644 index 00000000000..536ae9da2c2 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout-sets.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout-sets.schema.v1.json", + "sets": [ + { + "id": "component", + "tasks": [ + "component" + ] + }, + { + "id": "layout", + "tasks": [ + "layout" + ] + }, + { + "id": "layoutSet", + "tasks": [ + "layoutSet" + ] + }, + { + "id": "component2", + "tasks": [ + "component2" + ] + }, + { + "id": "layout2", + "tasks": [ + "layout2" + ] + }, + { + "id": "layoutSet2", + "tasks": [ + "layoutSet2" + ] + } + ], + "uiSettings": {} +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/layouts/Side1.json new file mode 100644 index 00000000000..a7e9078479d --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/layouts/Side1.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "" + }, + "id": "Summary2-FEI1HC", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "" + }, + "id": "Summary2-e2yYpk", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-qOn2O1", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-AVRSNf", + "taskId": "" + }, + "id": "Summary2-da0Tq6", + "type": "Summary2" + }, + { + "id": "NavigationButtons-7g3XcW", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/layouts/Side2.json new file mode 100644 index 00000000000..8b8ea337e26 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout/layouts/Side2.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-AVRSNf", + "type": "Input" + }, + { + "id": "NavigationButtons-wDmNQu", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/layouts/Side1.json new file mode 100644 index 00000000000..2530921cc4e --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/layouts/Side1.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-QCSonu", + "type": "Input" + }, + { + "target": { + "type": "layoutSet", + "id": "" + }, + "id": "Summary2-18hFaH", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1" + }, + "id": "Summary2-iZJ80j", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-V55C6Q", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-QCSonu" + }, + "id": "Summary2-aI91Tv", + "type": "Summary2" + }, + { + "id": "NavigationButtons-2hGPi1", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/layouts/Side2.json new file mode 100644 index 00000000000..6f3ad889e8f --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layout2/layouts/Side2.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "layout" + }, + "id": "Summary2-MfRiX8", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "layout" + }, + "id": "Summary2-66YavC", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "layout" + }, + "id": "Summary2-bmqvUb", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-AVRSNf", + "taskId": "layout" + }, + "id": "Summary2-uuKXTD", + "type": "Summary2" + }, + { + "id": "NavigationButtons-h0g3Wt", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/layouts/Side1.json new file mode 100644 index 00000000000..48f8a1f9cf7 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/layouts/Side1.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "id": "NavigationButtons-n2jUp6", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/layouts/Side2.json new file mode 100644 index 00000000000..6fa6e9455ff --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet/layouts/Side2.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-hqcYqo", + "type": "Input" + }, + { + "id": "NavigationButtons-QzkEka", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/Settings.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/Settings.json new file mode 100644 index 00000000000..6bb44cf6db8 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/Settings.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", + "pages": { + "order": [ + "Side1", + "Side2" + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/layouts/Side1.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/layouts/Side1.json new file mode 100644 index 00000000000..60fc1029c15 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/layouts/Side1.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "dataModelBindings": { + "simpleBinding": "" + }, + "id": "Input-wrspcN", + "type": "Input" + }, + { + "target": { + "type": "layoutSet", + "id": "" + }, + "id": "Summary2-00SFBO", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1" + }, + "id": "Summary2-4069IB", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "" + }, + "id": "Summary2-Orxmu1", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-wrspcN", + "taskId": "" + }, + "id": "Summary2-2YoJGY", + "type": "Summary2" + }, + { + "id": "NavigationButtons-KnXA9y", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/layouts/Side2.json b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/layouts/Side2.json new file mode 100644 index 00000000000..c49447d2904 --- /dev/null +++ b/backend/tests/Designer.Tests/_TestData/Repositories/testUser/ttd/app-with-summary2-components/App/ui/layoutSet2/layouts/Side2.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json", + "data": { + "layout": [ + { + "target": { + "type": "layoutSet", + "id": "", + "taskId": "layoutSet" + }, + "id": "Summary2-6VQ3LC", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side1", + "taskId": "layoutSet" + }, + "id": "Summary2-LZxPWb", + "type": "Summary2" + }, + { + "target": { + "type": "page", + "id": "Side2", + "taskId": "layoutSet" + }, + "id": "Summary2-El9z2Y", + "type": "Summary2" + }, + { + "target": { + "type": "component", + "id": "Input-hqcYqo", + "taskId": "layoutSet" + }, + "id": "Summary2-SMqpYV", + "type": "Summary2" + }, + { + "id": "NavigationButtons-5ukC2N", + "showBackButton": true, + "textResourceBindings": {}, + "type": "NavigationButtons" + } + ] + } +} \ No newline at end of file diff --git a/frontend/app-development/features/appContentLibrary/AppContentLibrary.test.tsx b/frontend/app-development/features/appContentLibrary/AppContentLibrary.test.tsx index c3537873930..dda27a7e455 100644 --- a/frontend/app-development/features/appContentLibrary/AppContentLibrary.test.tsx +++ b/frontend/app-development/features/appContentLibrary/AppContentLibrary.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { AppContentLibrary } from './AppContentLibrary'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { renderWithProviders } from '../../test/mocks'; @@ -11,7 +11,8 @@ import type { UserEvent } from '@testing-library/user-event'; import userEvent from '@testing-library/user-event'; import type { CodeList } from '@studio/components'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; -import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; +import type { OptionListData } from 'app-shared/types/OptionList'; +import type { QueryClient } from '@tanstack/react-query'; const uploadCodeListButtonTextMock = 'Upload Code List'; const updateCodeListButtonTextMock = 'Update Code List'; @@ -19,7 +20,7 @@ const updateCodeListIdButtonTextMock = 'Update Code List Id'; const codeListNameMock = 'codeListNameMock'; const newCodeListNameMock = 'newCodeListNameMock'; const codeListMock: CodeList = [{ value: '', label: '' }]; -const optionListsDataMock: OptionsListsResponse = [{ title: codeListNameMock, data: codeListMock }]; +const optionListsDataMock: OptionListData[] = [{ title: codeListNameMock, data: codeListMock }]; jest.mock( '../../../libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage', () => ({ @@ -51,7 +52,7 @@ describe('AppContentLibrary', () => { afterEach(jest.clearAllMocks); it('renders the AppContentLibrary with codeLists and images resources available in the content menu', () => { - renderAppContentLibrary(); + renderAppContentLibraryWithOptionLists(); const libraryTitle = screen.getByRole('heading', { name: textMock('app_content_library.landing_page.title'), }); @@ -63,14 +64,22 @@ describe('AppContentLibrary', () => { }); it('renders a spinner when waiting for option lists', () => { - renderAppContentLibrary({ shouldPutDataOnCache: false }); + renderAppContentLibrary(); const spinner = screen.getByText(textMock('general.loading')); expect(spinner).toBeInTheDocument(); }); + it('Renders an error message when the option lists query fails', async () => { + const getOptionLists = () => Promise.reject(new Error('Test error')); + renderAppContentLibrary({ queries: { getOptionLists } }); + await waitFor(expect(screen.queryByText(textMock('general.loading'))).not.toBeInTheDocument); + const errorMessage = screen.getByText(textMock('app_content_library.fetch_error')); + expect(errorMessage).toBeInTheDocument(); + }); + it('calls onUploadOptionList when onUploadCodeList is triggered', async () => { const user = userEvent.setup(); - renderAppContentLibrary(); + renderAppContentLibraryWithOptionLists(); await goToLibraryPage(user, 'code_lists'); const uploadCodeListButton = screen.getByRole('button', { name: uploadCodeListButtonTextMock }); await user.click(uploadCodeListButton); @@ -80,7 +89,7 @@ describe('AppContentLibrary', () => { it('renders success toast when onUploadOptionList is called successfully', async () => { const user = userEvent.setup(); - renderAppContentLibrary(); + renderAppContentLibraryWithOptionLists(); await goToLibraryPage(user, 'code_lists'); const uploadCodeListButton = screen.getByRole('button', { name: uploadCodeListButtonTextMock }); await user.click(uploadCodeListButton); @@ -93,7 +102,7 @@ describe('AppContentLibrary', () => { it('renders error toast when onUploadOptionList is rejected with unknown error code', async () => { const user = userEvent.setup(); const uploadOptionList = jest.fn().mockImplementation(() => Promise.reject({ response: {} })); - renderAppContentLibrary({ queries: { uploadOptionList } }); + renderAppContentLibraryWithOptionLists({ queries: { uploadOptionList } }); await goToLibraryPage(user, 'code_lists'); const uploadCodeListButton = screen.getByRole('button', { name: uploadCodeListButtonTextMock }); await user.click(uploadCodeListButton); @@ -105,7 +114,7 @@ describe('AppContentLibrary', () => { it('calls onUpdateOptionList when onUpdateCodeList is triggered', async () => { const user = userEvent.setup(); - renderAppContentLibrary(); + renderAppContentLibraryWithOptionLists(); await goToLibraryPage(user, 'code_lists'); const updateCodeListButton = screen.getByRole('button', { name: updateCodeListButtonTextMock }); await user.click(updateCodeListButton); @@ -120,7 +129,7 @@ describe('AppContentLibrary', () => { it('calls onUpdateOptionListId when onUpdateCodeListId is triggered', async () => { const user = userEvent.setup(); - renderAppContentLibrary(); + renderAppContentLibraryWithOptionLists(); await goToLibraryPage(user, 'code_lists'); const updateCodeListIdButton = screen.getByRole('button', { name: updateCodeListIdButtonTextMock, @@ -144,21 +153,30 @@ const goToLibraryPage = async (user: UserEvent, libraryPage: string) => { await user.click(libraryPageNavTile); }; -type renderAppContentLibraryProps = { +type RenderAppContentLibraryProps = { queries?: Partial<ServicesContextProps>; - shouldPutDataOnCache?: boolean; - optionListsData?: OptionsListsResponse; + queryClient?: QueryClient; }; const renderAppContentLibrary = ({ queries = {}, - shouldPutDataOnCache = true, - optionListsData = optionListsDataMock, -}: renderAppContentLibraryProps = {}) => { - const queryClientMock = createQueryClientMock(); - if (shouldPutDataOnCache) { - queryClientMock.setQueryData([QueryKey.OptionLists, org, app], optionListsData); - queryClientMock.setQueryData([QueryKey.OptionListsUsage, org, app], []); - } - renderWithProviders(queries, queryClientMock)(<AppContentLibrary />); + queryClient = createQueryClientMock(), +}: RenderAppContentLibraryProps = {}): void => { + renderWithProviders(queries, queryClient)(<AppContentLibrary />); }; + +function renderAppContentLibraryWithOptionLists( + props?: Omit<RenderAppContentLibraryProps, 'queryClient'>, +): void { + const queryClient = createQueryClientWithOptionsDataList(optionListsDataMock); + renderAppContentLibrary({ ...props, queryClient }); +} + +function createQueryClientWithOptionsDataList( + optionListDataList: OptionListData[] | undefined, +): QueryClient { + const queryClient = createQueryClientMock(); + queryClient.setQueryData([QueryKey.OptionLists, org, app], optionListDataList); + queryClient.setQueryData([QueryKey.OptionListsUsage, org, app], []); + return queryClient; +} diff --git a/frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx b/frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx index 92f65b621d1..4b35abe90ac 100644 --- a/frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx +++ b/frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx @@ -1,10 +1,16 @@ -import type { CodeListReference, CodeListWithMetadata } from '@studio/content-library'; +import type { + CodeListData, + CodeListReference, + CodeListWithMetadata, +} from '@studio/content-library'; import { ResourceContentLibraryImpl } from '@studio/content-library'; -import React from 'react'; +import type { ReactElement } from 'react'; +import React, { useCallback } from 'react'; + import { useOptionListsQuery, useOptionListsReferencesQuery } from 'app-shared/hooks/queries'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; -import { convertOptionsListsDataToCodeListsData } from './utils/convertOptionsListsDataToCodeListsData'; -import { StudioPageSpinner } from '@studio/components'; +import { mapToCodeListDataList } from './utils/mapToCodeListDataList'; +import { StudioPageError, StudioPageSpinner } from '@studio/components'; import { useTranslation } from 'react-i18next'; import type { ApiError } from 'app-shared/types/api/ApiError'; import { toast } from 'react-toastify'; @@ -14,57 +20,75 @@ import { useAddOptionListMutation, useUpdateOptionListMutation, useUpdateOptionListIdMutation, + useDeleteOptionListMutation, } from 'app-shared/hooks/mutations'; -import { mapToCodeListsUsage } from './utils/mapToCodeListsUsage'; +import { mapToCodeListUsages } from './utils/mapToCodeListUsages'; +import type { OptionListData } from 'app-shared/types/OptionList'; +import type { OptionListReferences } from 'app-shared/types/OptionListReferences'; +import { mergeQueryStatuses } from 'app-shared/utils/tanstackQueryUtils'; export function AppContentLibrary(): React.ReactElement { const { org, app } = useStudioEnvironmentParams(); const { t } = useTranslation(); - const { data: optionListsData, isPending: optionListsDataPending } = useOptionListsQuery( + const { data: optionListDataList, status: optionListDataListStatus } = useOptionListsQuery( org, app, ); - const { mutate: uploadOptionList } = useAddOptionListMutation(org, app, { - hideDefaultError: (error: AxiosError<ApiError>) => isErrorUnknown(error), - }); + const { data: optionListUsages, status: optionListUsagesStatus } = useOptionListsReferencesQuery( + org, + app, + ); + + const status = mergeQueryStatuses(optionListDataListStatus, optionListUsagesStatus); + + switch (status) { + case 'pending': + return <StudioPageSpinner spinnerTitle={t('general.loading')} />; + case 'error': + return <StudioPageError message={t('app_content_library.fetch_error')} />; + case 'success': + return ( + <AppContentLibraryWithData + optionListDataList={optionListDataList} + optionListUsages={optionListUsages} + /> + ); + } +} + +type AppContentLibraryWithDataProps = { + optionListDataList: OptionListData[]; + optionListUsages: OptionListReferences; +}; + +function AppContentLibraryWithData({ + optionListDataList, + optionListUsages, +}: AppContentLibraryWithDataProps): ReactElement { + const { org, app } = useStudioEnvironmentParams(); const { mutate: updateOptionList } = useUpdateOptionListMutation(org, app); const { mutate: updateOptionListId } = useUpdateOptionListIdMutation(org, app); - const { data: optionListsUsages, isPending: optionListsUsageIsPending } = - useOptionListsReferencesQuery(org, app); - - if (optionListsDataPending || optionListsUsageIsPending) - return <StudioPageSpinner spinnerTitle={t('general.loading')}></StudioPageSpinner>; + const { mutate: deleteOptionList } = useDeleteOptionListMutation(org, app); + const handleUpload = useUploadOptionList(org, app); - const codeListsData = convertOptionsListsDataToCodeListsData(optionListsData); + const codeListDataList: CodeListData[] = mapToCodeListDataList(optionListDataList); - const codeListsUsages: CodeListReference[] = mapToCodeListsUsage({ optionListsUsages }); + const codeListsUsages: CodeListReference[] = mapToCodeListUsages(optionListUsages); - const handleUpdateCodeListId = (optionListId: string, newOptionListId: string) => { + const handleUpdateCodeListId = (optionListId: string, newOptionListId: string): void => { updateOptionListId({ optionListId, newOptionListId }); }; - const handleUpload = (file: File) => { - uploadOptionList(file, { - onSuccess: () => { - toast.success(t('ux_editor.modal_properties_code_list_upload_success')); - }, - onError: (error: AxiosError<ApiError>) => { - if (isErrorUnknown(error)) { - toast.error(t('ux_editor.modal_properties_code_list_upload_generic_error')); - } - }, - }); - }; - - const handleUpdate = ({ title, codeList }: CodeListWithMetadata) => { - updateOptionList({ optionListId: title, optionsList: codeList }); + const handleUpdate = ({ title, codeList }: CodeListWithMetadata): void => { + updateOptionList({ optionListId: title, optionList: codeList }); }; const { getContentResourceLibrary } = new ResourceContentLibraryImpl({ pages: { codeList: { props: { - codeListsData, + codeListsData: codeListDataList, + onDeleteCodeList: deleteOptionList, onUpdateCodeListId: handleUpdateCodeListId, onUpdateCodeList: handleUpdate, onUploadCodeList: handleUpload, @@ -82,3 +106,25 @@ export function AppContentLibrary(): React.ReactElement { return <div>{getContentResourceLibrary()}</div>; } + +function useUploadOptionList(org: string, app: string): (file: File) => void { + const { mutate: uploadOptionList } = useAddOptionListMutation(org, app, { + hideDefaultError: (error: AxiosError<ApiError>) => isErrorUnknown(error), + }); + const { t } = useTranslation(); + + return useCallback( + (file: File) => + uploadOptionList(file, { + onSuccess: () => { + toast.success(t('ux_editor.modal_properties_code_list_upload_success')); + }, + onError: (error: AxiosError<ApiError>) => { + if (isErrorUnknown(error)) { + toast.error(t('ux_editor.modal_properties_code_list_upload_generic_error')); + } + }, + }), + [uploadOptionList, t], + ); +} diff --git a/frontend/app-development/features/appContentLibrary/utils/convertOptionsListsDataToCodeListsData.ts b/frontend/app-development/features/appContentLibrary/utils/convertOptionsListsDataToCodeListsData.ts deleted file mode 100644 index e9d386db542..00000000000 --- a/frontend/app-development/features/appContentLibrary/utils/convertOptionsListsDataToCodeListsData.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { OptionsListData, OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; -import type { CodeListData } from '@studio/content-library'; - -export const convertOptionsListsDataToCodeListsData = (optionListsData: OptionsListsResponse) => { - const codeListsData = []; - optionListsData.map((optionListData) => { - const codeListData = convertOptionsListDataToCodeListData(optionListData); - codeListsData.push(codeListData); - }); - return codeListsData; -}; - -const convertOptionsListDataToCodeListData = (optionListData: OptionsListData) => { - const codeListData: CodeListData = { - title: optionListData.title, - data: optionListData.data, - hasError: optionListData.hasError, - }; - return codeListData; -}; diff --git a/frontend/app-development/features/appContentLibrary/utils/convertOptionsListsDataToCodeListsData.test.ts b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListDataList.test.ts similarity index 62% rename from frontend/app-development/features/appContentLibrary/utils/convertOptionsListsDataToCodeListsData.test.ts rename to frontend/app-development/features/appContentLibrary/utils/mapToCodeListDataList.test.ts index 7d67033df69..1b87582e0f5 100644 --- a/frontend/app-development/features/appContentLibrary/utils/convertOptionsListsDataToCodeListsData.test.ts +++ b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListDataList.test.ts @@ -1,11 +1,11 @@ import type { CodeListData } from '@studio/content-library'; -import { convertOptionsListsDataToCodeListsData } from './convertOptionsListsDataToCodeListsData'; -import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; +import { mapToCodeListDataList } from './mapToCodeListDataList'; +import type { OptionListData } from 'app-shared/types/OptionList'; -describe('convertOptionsListsDataToCodeListsData', () => { +describe('mapToCodeListDataList', () => { it('converts option lists data to code lists data correctly', () => { const optionListId: string = 'optionListId'; - const optionListsData: OptionsListsResponse = [ + const optionListDataList: OptionListData[] = [ { title: optionListId, data: [ @@ -15,7 +15,7 @@ describe('convertOptionsListsDataToCodeListsData', () => { hasError: false, }, ]; - const result: CodeListData[] = convertOptionsListsDataToCodeListsData(optionListsData); + const result: CodeListData[] = mapToCodeListDataList(optionListDataList); expect(result).toEqual([ { title: optionListId, @@ -30,20 +30,20 @@ describe('convertOptionsListsDataToCodeListsData', () => { it('sets hasError to true in result when optionListsResponse returns an option list with error', () => { const optionListId: string = 'optionListId'; - const optionListsData: OptionsListsResponse = [ + const optionListDataList: OptionListData[] = [ { title: optionListId, data: null, hasError: true, }, ]; - const result: CodeListData[] = convertOptionsListsDataToCodeListsData(optionListsData); + const result: CodeListData[] = mapToCodeListDataList(optionListDataList); expect(result).toEqual([{ title: optionListId, data: null, hasError: true }]); }); it('returns a result with empty code list data array when the input option list data is empty', () => { - const optionListsData: OptionsListsResponse = []; - const result: CodeListData[] = convertOptionsListsDataToCodeListsData(optionListsData); + const optionListDataList: OptionListData[] = []; + const result: CodeListData[] = mapToCodeListDataList(optionListDataList); expect(result).toEqual([]); }); }); diff --git a/frontend/app-development/features/appContentLibrary/utils/mapToCodeListDataList.ts b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListDataList.ts new file mode 100644 index 00000000000..f03615e8e3d --- /dev/null +++ b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListDataList.ts @@ -0,0 +1,11 @@ +import type { OptionListData } from 'app-shared/types/OptionList'; +import type { CodeListData } from '@studio/content-library'; + +export const mapToCodeListDataList = (optionListDataList: OptionListData[]): CodeListData[] => + optionListDataList.map(convertOptionListDataToCodeListData); + +const convertOptionListDataToCodeListData = (optionListData: OptionListData): CodeListData => ({ + title: optionListData.title, + data: optionListData.data, + hasError: optionListData.hasError, +}); diff --git a/frontend/app-development/features/appContentLibrary/utils/mapToCodeListUsages.test.ts b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListUsages.test.ts new file mode 100644 index 00000000000..866bdf2ae47 --- /dev/null +++ b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListUsages.test.ts @@ -0,0 +1,35 @@ +import type { CodeListIdSource } from '@studio/content-library'; +import { mapToCodeListUsages } from './mapToCodeListUsages'; +import type { OptionListReferences } from 'app-shared/types/OptionListReferences'; + +const optionListId: string = 'optionListId'; +const optionListIdSources: CodeListIdSource[] = [ + { + layoutSetId: 'layoutSetId', + layoutName: 'layoutName', + componentIds: ['componentId1', 'componentId2'], + }, +]; +const optionListUsages: OptionListReferences = [ + { + optionListId, + optionListIdSources, + }, +]; + +describe('mapToCodeListUsages', () => { + it('maps optionListsUsages to codeListUsages', () => { + const codeListUsages = mapToCodeListUsages(optionListUsages); + expect(codeListUsages).toEqual([ + { + codeListId: optionListId, + codeListIdSources: optionListIdSources, + }, + ]); + }); + + it('maps undefined optionListUsages to empty array', () => { + const codeListUsages = mapToCodeListUsages(undefined); + expect(codeListUsages).toEqual([]); + }); +}); diff --git a/frontend/app-development/features/appContentLibrary/utils/mapToCodeListUsages.ts b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListUsages.ts new file mode 100644 index 00000000000..389a8be27fe --- /dev/null +++ b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListUsages.ts @@ -0,0 +1,12 @@ +import type { OptionListReferences } from 'app-shared/types/OptionListReferences'; +import type { CodeListReference } from '@studio/content-library'; + +export const mapToCodeListUsages = ( + optionListUsages: OptionListReferences, +): CodeListReference[] => { + if (!optionListUsages) return []; + return optionListUsages.map((optionListsUsage) => ({ + codeListId: optionListsUsage.optionListId, + codeListIdSources: optionListsUsage.optionListIdSources, + })); +}; diff --git a/frontend/app-development/features/appContentLibrary/utils/mapToCodeListsUsage.test.ts b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListsUsage.test.ts deleted file mode 100644 index 1be4ee745d7..00000000000 --- a/frontend/app-development/features/appContentLibrary/utils/mapToCodeListsUsage.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { CodeListIdSource } from '@studio/content-library'; -import { mapToCodeListsUsage } from './mapToCodeListsUsage'; -import type { OptionListsReferences } from 'app-shared/types/api/OptionsLists'; - -const optionListId: string = 'optionListId'; -const optionListIdSources: CodeListIdSource[] = [ - { - layoutSetId: 'layoutSetId', - layoutName: 'layoutName', - componentIds: ['componentId1', 'componentId2'], - }, -]; -const optionListsUsages: OptionListsReferences = [ - { - optionListId, - optionListIdSources, - }, -]; - -describe('mapToCodeListsUsage', () => { - it('maps optionListsUsage to codeListUsage', () => { - const codeListUsage = mapToCodeListsUsage({ optionListsUsages }); - expect(codeListUsage).toEqual([ - { - codeListId: optionListId, - codeListIdSources: optionListIdSources, - }, - ]); - }); - - it('maps undefined optionListsUsage to empty array', () => { - const codeListUsage = mapToCodeListsUsage({ optionListsUsages: undefined }); - expect(codeListUsage).toEqual([]); - }); -}); diff --git a/frontend/app-development/features/appContentLibrary/utils/mapToCodeListsUsage.ts b/frontend/app-development/features/appContentLibrary/utils/mapToCodeListsUsage.ts deleted file mode 100644 index 51b13cb1e95..00000000000 --- a/frontend/app-development/features/appContentLibrary/utils/mapToCodeListsUsage.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { OptionListsReferences } from 'app-shared/types/api/OptionsLists'; -import type { CodeListReference } from '@studio/content-library'; - -type MapToCodeListsUsageProps = { - optionListsUsages: OptionListsReferences; -}; - -export const mapToCodeListsUsage = ({ - optionListsUsages, -}: MapToCodeListsUsageProps): CodeListReference[] => { - const codeListsUsages: CodeListReference[] = []; - if (!optionListsUsages) return codeListsUsages; - optionListsUsages.map((optionListsUsage) => - codeListsUsages.push({ - codeListId: optionListsUsage.optionListId, - codeListIdSources: optionListsUsage.optionListIdSources, - }), - ); - return codeListsUsages; -}; diff --git a/frontend/app-development/features/overview/components/News/NewsContent/news.nb.json b/frontend/app-development/features/overview/components/News/NewsContent/news.nb.json index 01b971e82fd..c7085d5cd5d 100644 --- a/frontend/app-development/features/overview/components/News/NewsContent/news.nb.json +++ b/frontend/app-development/features/overview/components/News/NewsContent/news.nb.json @@ -1,6 +1,11 @@ { "$schema": "news.schema.json", "news": [ + { + "date": "2025-01-17", + "title": "Nå kan du ha flere datamodeller i et prosess-steg!", + "content": "Du kan nå koble komponenter i et prosess-steg til andre datamodeller, ikke bare til den overordnede datamodellen for steget. Dette gir deg nye muligheter til å håndtere data i appene dine." + }, { "date": "2024-11-29", "title": "Prøv nytt design på Utforming!", diff --git a/frontend/app-development/package.json b/frontend/app-development/package.json index 981cfe8837f..321f03b2d01 100644 --- a/frontend/app-development/package.json +++ b/frontend/app-development/package.json @@ -20,12 +20,12 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-i18next": "15.4.0", - "react-router-dom": "6.28.1" + "react-router-dom": "6.28.2" }, "devDependencies": { "cross-env": "7.0.3", "jest": "29.7.0", - "typescript": "5.7.2", + "typescript": "5.7.3", "webpack": "5.97.1", "webpack-dev-server": "5.2.0" }, diff --git a/frontend/app-preview/package.json b/frontend/app-preview/package.json index e63098a32bc..75e61ad081a 100644 --- a/frontend/app-preview/package.json +++ b/frontend/app-preview/package.json @@ -11,12 +11,12 @@ "dependencies": { "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.28.1" + "react-router-dom": "6.28.2" }, "devDependencies": { "cross-env": "7.0.3", "jest": "29.7.0", - "typescript": "5.7.2", + "typescript": "5.7.3", "webpack": "5.97.1", "webpack-dev-server": "5.2.0" }, diff --git a/frontend/dashboard/package.json b/frontend/dashboard/package.json index e7424eef66e..6c1674d877b 100644 --- a/frontend/dashboard/package.json +++ b/frontend/dashboard/package.json @@ -11,12 +11,12 @@ "dependencies": { "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.28.1" + "react-router-dom": "6.28.2" }, "devDependencies": { "cross-env": "7.0.3", "jest": "29.7.0", - "typescript": "5.7.2", + "typescript": "5.7.3", "webpack": "5.97.1", "webpack-dev-server": "5.2.0" }, diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 01c776b2c86..05933cd758c 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -12,9 +12,14 @@ "api_errors.ResourceNotFound": "Studio prøver å finne en fil som ikke finnes.", "api_errors.Unauthorized": "Handlingen du prøver å utføre krever rettigheter du ikke har. Du blir nå logget ut.", "api_errors.UploadedImageNotValid": "Det opplastede bildet er ikke en gyldig filtype", + "app_content_library.code_lists.clear_search_button_label": "Fjern søkeord", "app_content_library.code_lists.code_list_accordion_title": "Kodelistenavn: {{codeListTitle}}", "app_content_library.code_lists.code_list_accordion_usage_sub_title_plural": "Kodelisten brukes i {{codeListUsagesCount}} komponenter.", "app_content_library.code_lists.code_list_accordion_usage_sub_title_single": "Kodelisten brukes i {{codeListUsagesCount}} komponent.", + "app_content_library.code_lists.code_list_delete": "Slett kodeliste", + "app_content_library.code_lists.code_list_delete_disabled_title": "Før du kan å slette kodelisten, må du fjerne den fra der den er brukt i appen.", + "app_content_library.code_lists.code_list_delete_enabled_title": "Slett kodelisten fra biblioteket.", + "app_content_library.code_lists.code_list_edit_id_disabled_title": "Redigering er ikke tilgjengelig når kodelisten er tatt i bruk.", "app_content_library.code_lists.code_list_edit_id_label": "Navn på kodeliste", "app_content_library.code_lists.code_list_edit_id_title": "Rediger navn på kodelisten {{codeListName}}", "app_content_library.code_lists.code_list_show_usage": "Se hvor kodelisten er tatt i bruk", @@ -38,6 +43,7 @@ "app_content_library.code_lists.save_new_code_list": "Lagre", "app_content_library.code_lists.search_label": "Søk på kodelister", "app_content_library.code_lists.upload_code_list": "Last opp din egen kodeliste", + "app_content_library.fetch_error": "Det har oppstått et problem ved henting av data til biblioteket.", "app_content_library.images.info_box.description": "Du kan bruke bildene i biblioteket til å legge inn bilder i skjemaet. Du kan også laste opp et bilde med organisasjonens logo, og legge det som logobilde i innstillingene for appen.", "app_content_library.images.info_box.title": "Hva kan du bruke bildene til?", "app_content_library.images.no_content": "Dette biblioteket har ingen bilder", @@ -153,11 +159,16 @@ "code_list_editor.text_resource.label.select": "Finn ledetekst for verdi nummer {{number}}", "code_list_editor.text_resource.label.value": "Oppgi ledetekst for verdi nummer {{number}}", "code_list_editor.value_item": "Verdi for alternativ {{number}}", + "contact.altinn_servicedesk.content": "Er du tjenesteeier og har du behov for hjelp? Ta kontakt med oss!", + "contact.altinn_servicedesk.heading": "Altinn Servicedesk", "contact.email.content": "Du kan skrive en e-post til Altinn servicedesk hvis du har spørsmål om å opprette organisasjoner eller miljøer, opplever tekniske problemer eller har spørsmål om dokumentasjonen eller andre ting.", "contact.email.heading": "Send e-post", "contact.github_issue.content": "Hvis du har behov for funksjonalitet eller ser feil og mangler i Studio som vi må fikse, kan du opprette en sak i Github, så ser vi på den.", "contact.github_issue.heading": "Rapporter feil og mangler til oss", "contact.github_issue.link_label": "Opprett sak i Github", + "contact.serviceDesk.email": "<b>E-post:</b> <a>tjenesteeier@altinn.no</a>", + "contact.serviceDesk.emergencyPhone": "<b>Vakttelefon:</b> <a>94 49 00 02</a> (tilgjengelig kl. 15:45–07:00)", + "contact.serviceDesk.phone": "<b>Telefon:</b> <a>75 00 62 99</a>", "contact.slack.content": "Hvis du har spørsmål om hvordan du bygger en app, kan du snakke direkte med utviklingsteamet i Altinn Studio på Slack. De hjelper deg med å", "contact.slack.content_list": "<0>bygge appene slik du ønsker</0><0>svare på spørsmål og veilede deg</0><0>ta imot innspill på ny funksjonalitet</0>", "contact.slack.heading": "Skriv melding til oss Slack", @@ -622,8 +633,13 @@ "policy_editor.access_package_remove": "Fjern tilgangspakke {{packageName}}", "policy_editor.access_package_search": "Søk i tilgangspakker", "policy_editor.access_package_services": "Tjenester i denne tilgangspakken:", - "policy_editor.access_package_warning_body": "Altinn-rollene fases snart ut, og da vil rollene som er lagt til ikke lenger være gyldige. Du må derfor legge til minst én tilgangspakke for å unngå at regelen blir ugyldig.", - "policy_editor.access_package_warning_header": "Tilgangspakker tar over for Altinn-rollene", + "policy_editor.access_package_warning_body1": "Snart faser vi ut Altinn-rollene. Da blir ikke rollene som er lagt til i denne regelen lenger gyldige. I stedet for roller skal du bruke tilgangspakker. <0 href=\"{{accessPackageLink}}\">Les mer om bruk av tilgangspakker</0>.", + "policy_editor.access_package_warning_body2": "Tilgangspakker kan foreløpig ikke benyttes til tilgangsstyring, men vi anbefaler at du allerede nå knytter tjenesten til minst én tilgangspakke. Dette vil gjøre overgangen enklere når funksjonaliteten blir aktivert.", + "policy_editor.access_package_warning_header": "Tilgangspakker tar snart over for Altinn-rollene", + "policy_editor.access_package_warning_header2": "Merk:", + "policy_editor.access_package_warning_listitem1": "Du kan ikke teste tilgangene pakkene gir, før miljøene støtter det.", + "policy_editor.access_package_warning_listitem2": "Frem til våren 2025 må du fortsatt bruke roller for å styre tilgangen til tjenester.", + "policy_editor.access_package_warning_listitem3": "Velg tilgangspakker som tilsvarer de rollene du bruker i dag.", "policy_editor.action_complete": "Bekreft mottatt tjenesteeier", "policy_editor.action_confirm": "Bekreft", "policy_editor.action_delete": "Slett", @@ -1191,7 +1207,7 @@ "ux_editor.component_category.select": "Flervalg", "ux_editor.component_category.text": "Tekst", "ux_editor.component_deletion_confirm": "Ja, slett komponenten", - "ux_editor.component_deletion_text": "Er du sikker på at du vil slette denne komponenten?", + "ux_editor.component_deletion_text": "Er du sikker på at du vil slette denne komponenten?\nAlle Summary2-komponenter knyttet til denne komponenten vil også bli slettet.", "ux_editor.component_dropdown_set_preselected": "Sett forhåndsvalgt verdi for nedtrekksliste", "ux_editor.component_group_deletion_text": "Er du sikker på at du vil slette denne gruppen?\nAlle komponenter i denne gruppen blir også slettet", "ux_editor.component_help_text.Accordion": "Med Trekkspilliste kan du presentere mye innhold på liten plass, i en eller flere rader. Brukerne kan klikke på hele raden for å vise eller skjule innholdet under.", @@ -1345,7 +1361,6 @@ "ux_editor.component_properties.hiddenRow": "Angi hvilke rader som skal skjules", "ux_editor.component_properties.hideBottomBorder": "Skjul skillelinjen under komponenten", "ux_editor.component_properties.hideChangeButton": "Skjul Endre-knapp", - "ux_editor.component_properties.hideEmptyFields": "Skjul tomme felter", "ux_editor.component_properties.hideValidationMessages": "Skjul valideringsmeldinger", "ux_editor.component_properties.icon": "Ikon", "ux_editor.component_properties.id": "ID", @@ -1378,19 +1393,11 @@ "ux_editor.component_properties.optionalIndicator": "Vis valgfri-indikator på ledetekst", "ux_editor.component_properties.options": "Alternativer", "ux_editor.component_properties.optionsId": "Kodeliste", - "ux_editor.component_properties.overrides": "Overstyringer", - "ux_editor.component_properties.overrides_description": "Overstyringer per komponent for oppsummeringen", - "ux_editor.component_properties.overrides_is_compact": "Kompakt visning", - "ux_editor.component_properties.overrides_list": "Liste", - "ux_editor.component_properties.overrides_not_set": "Ikke satt", - "ux_editor.component_properties.overrides_string": "Tekst", - "ux_editor.component_properties.overrides_type": "Vis type", "ux_editor.component_properties.pageBreak": "PDF-innstillinger (pageBreak)", "ux_editor.component_properties.pageRef": "Navnet til siden det gjelder (pageRef)", "ux_editor.component_properties.pagination": "Sidenummerering", "ux_editor.component_properties.position": "Plassering av valuta", "ux_editor.component_properties.preselectedOptionIndex": "Angi det valget som skal være forhåndsvalgt.", - "ux_editor.component_properties.preselected_help_text": "Eksempel: Hvis du har 5 valg, kan du bruke tall fra 0-4.", "ux_editor.component_properties.queryParameters": "Parametere i spørringen", "ux_editor.component_properties.readOnly": "Feltet kan kun leses", "ux_editor.component_properties.receiver": "Den som mottar skjemaet", @@ -1452,12 +1459,18 @@ "ux_editor.component_properties.subform.no_existing_layout_set_instructions_header": "Slik gjør du:", "ux_editor.component_properties.subform.selected_layout_set_label": "Underskjema", "ux_editor.component_properties.subform.selected_layout_set_title": "Valgt underskjemakobling er {{subform}}", - "ux_editor.component_properties.summary.add_override": "Legg til overstyring", - "ux_editor.component_properties.summary.override.component_id": "ID på komponenten", - "ux_editor.component_properties.summary.override.empty_field_text": "Tekst for tomme felter", - "ux_editor.component_properties.summary.override.force_show": "Vis alltid feltet", + "ux_editor.component_properties.summary.add_override": "Lag en ny overstyring", + "ux_editor.component_properties.summary.override.choose_component": "Velg komponent", + "ux_editor.component_properties.summary.override.description": "Overstyringer per komponent for oppsummeringen", + "ux_editor.component_properties.summary.override.display_type": "Visningstype", + "ux_editor.component_properties.summary.override.display_type.list": "Liste", + "ux_editor.component_properties.summary.override.display_type.not_set": "Ikke satt", + "ux_editor.component_properties.summary.override.display_type.string": "Tekst", + "ux_editor.component_properties.summary.override.empty_field_text": "Tekst du vil vise i tomme felt", "ux_editor.component_properties.summary.override.hidden": "Skjul feltet", "ux_editor.component_properties.summary.override.hide_empty_fields": "Skjul tomme felter", + "ux_editor.component_properties.summary.override.show_component": "Vis komponenten", + "ux_editor.component_properties.summary.overrides": "Overstyringer", "ux_editor.component_properties.summaryDelimiter": "Skillelinje for sammendragsvisningsceller", "ux_editor.component_properties.tableColumns": "Innstillinger for kolonner", "ux_editor.component_properties.tableHeaders": "Felter som skal vises i tabellens overskrift", @@ -1495,6 +1508,7 @@ "ux_editor.component_properties_description.pageBreak": "Valgfri sideskift før eller etter komponenten i PDF", "ux_editor.component_properties_description.pagination": "Pagineringsvalg for repeterende gruppe", "ux_editor.component_properties_help_text.hidden": "Hvis du velger å skjule et felt, kan du sette betingelser for når det skal skjules under Valg for dynamikk.", + "ux_editor.component_properties_help_text.preselectedOptionIndex": "Eksempel: Hvis du har 5 valg, kan du bruke tall fra 0-4.", "ux_editor.component_title.Accordion": "Trekkspilliste", "ux_editor.component_title.AccordionGroup": "Nestet trekkspilliste", "ux_editor.component_title.ActionButton": "Handlingsknapp", @@ -1795,16 +1809,13 @@ "ux_editor.page_config_pdf_delete_existing_pdf": "Slett eksisterende PDF", "ux_editor.page_config_pdf_exclude_components_from_default_pdf": "Velg hvilke komponenter fra siden som skal skjules i standard PDF", "ux_editor.page_config_pdf_exclude_page_from_default_pdf": "Ekskluder siden fra standard PDF", - "ux_editor.page_delete_text": "Er du sikker på at du vil slette denne siden?\nAlt innholdet på siden vil bli fjernet.", + "ux_editor.page_delete_text": "Er du sikker på at du vil slette denne siden?\nAlt innholdet på siden vil bli fjernet.\nAlle Summary2-komponenter knyttet til denne siden vil også bli slettet.", "ux_editor.page_menu_down": "Flytt ned", "ux_editor.page_menu_edit": "Gi nytt navn", "ux_editor.page_menu_up": "Flytt opp", "ux_editor.pages_add": "Legg til ny side", "ux_editor.pages_error_empty": "Navnet kan ikke være tomt.", - "ux_editor.pages_error_format": "Navnet må bestå av bokstaver (a-z), tall, eller \"-\", \"_\" og \".\".", - "ux_editor.pages_error_invalid_format": "Navnet kan ikke inneholde punktum.", - "ux_editor.pages_error_length": "Navnet kan ikke være lengre enn 30 tegn.", - "ux_editor.pages_error_unique": "Navnet må være unikt.", + "ux_editor.pages_error_unique": "Det finnes allerede en side med dette navnet. Bruk et annet navn.", "ux_editor.preview": "Forhåndsvisning", "ux_editor.properties_panel.images.add_image_tab_title": "Legg til bilde", "ux_editor.properties_panel.images.cancel_image_upload": "Avbryt opplastning", @@ -1896,13 +1907,14 @@ "ux_editor.upload_file_error_too_large": "Kunne ikke laste opp filen. Den er for stor.", "ux_editor.url_label": "Lenke", "ux_editor.warning": "Advarsel", - "validation_errors.file_name_invalid": "Filnavnet er ugyldig. Du kan bruke tall, understrek, bindestrek, og store/små bokstaver fra det engelske alfabetet.", + "validation_errors.file_name_invalid": "Filnavnet er ugyldig. Det må ha mellom 2 og 28 tegn og kan inneholde tall, understrek, bindestrek, og store/små bokstaver fra det engelske alfabetet.", "validation_errors.file_name_occupied": "Det finnes allerede en kodeliste med det navnet. Bruk et annet navn.", "validation_errors.length": "Antall tillatte tegn er {{0}}.", "validation_errors.max": "Største gyldig verdi er {{0}}.", "validation_errors.maxLength": "Bruk {{number}} eller færre tegn.", "validation_errors.min": "Minste gyldig verdi er {{0}}.", "validation_errors.minLength": "Bruk {{0}} eller flere tegn.", + "validation_errors.name_invalid": "Navnet er ugyldig. Det må ha mellom 2 og 28 tegn og kan inneholde tall, understrek, bindestrek, og store/små bokstaver fra det engelske alfabetet.", "validation_errors.numbers_only": "Du kan bare bruke sifre.", "validation_errors.pattern": "Feil format eller verdi.", "validation_errors.required": "Feltet må fylles ut.", diff --git a/frontend/libs/studio-components/package.json b/frontend/libs/studio-components/package.json index 5ff31908319..66b0524622c 100644 --- a/frontend/libs/studio-components/package.json +++ b/frontend/libs/studio-components/package.json @@ -20,17 +20,17 @@ "uuid": "10.0.0" }, "devDependencies": { - "@chromatic-com/storybook": "3.2.3", - "@storybook/addon-essentials": "8.4.7", - "@storybook/addon-interactions": "8.4.7", - "@storybook/addon-links": "8.4.7", + "@chromatic-com/storybook": "3.2.4", + "@storybook/addon-essentials": "8.5.0", + "@storybook/addon-interactions": "8.5.0", + "@storybook/addon-links": "8.5.0", "@storybook/addon-webpack5-compiler-swc": "2.0.0", - "@storybook/blocks": "8.4.7", - "@storybook/react": "8.4.7", - "@storybook/react-webpack5": "8.4.7", - "@storybook/test": "8.4.7", + "@storybook/blocks": "8.5.0", + "@storybook/react": "8.5.0", + "@storybook/react-webpack5": "8.5.0", + "@storybook/test": "8.5.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.1.0", + "@testing-library/react": "16.2.0", "@types/jest": "^29.5.5", "eslint": "8.57.1", "eslint-plugin-storybook": "^0.11.0", @@ -38,6 +38,6 @@ "jest-environment-jsdom": "^29.7.0", "storybook": "^8.0.4", "ts-jest": "^29.1.1", - "typescript": "5.7.2" + "typescript": "5.7.3" } } diff --git a/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.module.css b/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.module.css index f7e6197c3ec..ae0432adb7f 100644 --- a/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.module.css +++ b/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.module.css @@ -5,15 +5,14 @@ gap: var(--fds-spacing-2); } -.prefixIcon { - grid-template-columns: auto 1fr; -} - .container:has(:nth-child(3)) { grid-template-columns: auto 1fr auto; } .label { + display: flex; + align-items: center; + gap: var(--fds-spacing-1); font-weight: 500; } diff --git a/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.test.tsx b/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.test.tsx index 8c9f0ac068a..0003b8ae48e 100644 --- a/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.test.tsx @@ -31,14 +31,4 @@ describe('StudioDisplayTile', () => { render(<StudioDisplayTile {...defaultProps} showPadlock={false} />); expect(screen.queryByTestId(padlockIconTestId)).not.toBeInTheDocument(); }); - - it('should not assign prefix icon className by default', () => { - render(<StudioDisplayTile {...defaultProps} />); - expect(screen.getByLabelText(label)).not.toHaveClass('prefixIcon'); - }); - - it('should assign prefix icon className when prefix icon is set', () => { - render(<StudioDisplayTile {...defaultProps} icon={<svg />} />); - expect(screen.getByLabelText(label)).toHaveClass('prefixIcon'); - }); }); diff --git a/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.tsx b/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.tsx index 7bf3928ca65..5078fd48842 100644 --- a/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.tsx +++ b/frontend/libs/studio-components/src/components/StudioDisplayTile/StudioDisplayTile.tsx @@ -23,14 +23,13 @@ const StudioDisplayTile = forwardRef<HTMLDivElement, StudioDisplayTileProps>( }: StudioDisplayTileProps, ref, ): React.ReactElement => { - const hasPrefixIcon = !!icon; - const className = cn(givenClassName, classes.container, hasPrefixIcon && classes.prefixIcon); + const className = cn(givenClassName, classes.container); return ( <div {...rest} aria-label={label} className={className} ref={ref}> - {icon} <div className={classes.ellipsis}> <Label size='small' className={classes.label}> + {icon} {label} </Label> <Paragraph size='small' className={classes.ellipsis}> diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.module.css b/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.module.css new file mode 100644 index 00000000000..6eb7f377974 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.module.css @@ -0,0 +1,34 @@ +.wrapper { + display: flex; + flex-direction: column; + align-items: center; +} + +.buttonWrapper { + display: flex; + gap: var(--fds-spacing-4); + margin-block: var(--fds-spacing-4); +} + +.statusBarContainer { + display: flex; + gap: 4px; + margin-top: 1rem; +} + +.statusBarPiece { + flex: 1; + height: 10px; + background: #e0e0e0; + border-radius: 50%; + width: 10px; + margin: 0; +} + +.statusBarPiece.active { + background: var(--fds-semantic-surface-action-first-default); +} + +.icon { + font-size: var(--fds-sizing-5); +} diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.stories.tsx b/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.stories.tsx new file mode 100644 index 00000000000..646abcee233 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.stories.tsx @@ -0,0 +1,74 @@ +import React, { type ChangeEvent, useState } from 'react'; +import type { Meta, StoryFn } from '@storybook/react'; +import { StudioPaginatedContent } from './StudioPaginatedContent'; +import { StudioParagraph } from '../StudioParagraph'; +import { StudioTextfield } from '../StudioTextfield'; +import { usePagination } from './hooks/usePagination'; +import { type StudioPaginatedItem } from './types/StudioPaginatedItem'; + +type ChildrenProps = { + onChange: (event: ChangeEvent<HTMLInputElement>) => void; + value: string; +}; +const Children1 = ({ onChange, value }: ChildrenProps) => { + return ( + <div> + <StudioParagraph>Children 1</StudioParagraph> + <StudioTextfield + size='sm' + label='Please enter the value "3" to proceed to the next page.' + onChange={onChange} + value={value} + /> + </div> + ); +}; +const Children2 = () => <StudioParagraph size='sm'>Children 2</StudioParagraph>; +const Children3 = () => <StudioParagraph size='sm'>Children 3</StudioParagraph>; +const Children4 = () => <StudioParagraph size='sm'>Children 4</StudioParagraph>; + +type Story = StoryFn<typeof StudioPaginatedContent>; + +const meta: Meta = { + title: 'Components/StudioPaginatedContent', + component: StudioPaginatedContent, + argTypes: {}, +}; + +export const Preview: Story = () => { + const [inputValue, setInputValue] = useState(''); + + const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const value = event.target.value; + setInputValue(value); + }; + + const items: StudioPaginatedItem[] = [ + { + pageContent: <Children1 key={1} value={inputValue} onChange={handleInputChange} />, + validationRuleForNextButton: inputValue === '3', + }, + { + pageContent: <Children2 key={2} />, + }, + { + pageContent: <Children3 key={3} />, + }, + { + pageContent: <Children4 key={4} />, + }, + ]; + const { currentPage, pages, navigation } = usePagination(items); + + return ( + <StudioPaginatedContent + navigationButtonTexts={{ previous: 'Previous', next: 'Next' }} + componentToRender={pages[currentPage]} + navigation={navigation} + currentPageNumber={currentPage} + totalPages={pages.length} + /> + ); +}; + +export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.test.tsx b/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.test.tsx new file mode 100644 index 00000000000..4a11b1b4bc9 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.test.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { StudioPaginatedContent, type StudioPaginatedContentProps } from './StudioPaginatedContent'; + +const navigationMock: StudioPaginatedContentProps['navigation'] = { + canGoNext: true, + canGoPrevious: true, + onNext: jest.fn(), + onPrevious: jest.fn(), +}; + +const buttonTextsMock: StudioPaginatedContentProps['navigationButtonTexts'] = { + previous: 'Previous', + next: 'Next', +}; + +const defaultProps: StudioPaginatedContentProps = { + totalPages: 5, + currentPageNumber: 2, + componentToRender: <div>Content</div>, + navigationButtonTexts: buttonTextsMock, + navigation: navigationMock, +}; + +describe('StudioPaginatedContent', () => { + it('renders the componentToRender', () => { + renderStudioPaginatedContent(); + expect(screen.getByText('Content')).toBeInTheDocument(); + }); + + it('renders the correct number of navigation circles', () => { + renderStudioPaginatedContent(); + + const circles = screen.getAllByRole('status'); + expect(circles.length).toBe(defaultProps.totalPages); + }); + + it('disables the previous button when canGoPrevious is false', () => { + renderStudioPaginatedContent({ + navigation: { ...navigationMock, canGoPrevious: false }, + }); + + expect(screen.getByText('Previous')).toBeDisabled(); + }); + + it('enables the next button when canGoNext is undefined', () => { + renderStudioPaginatedContent({ + navigation: { ...navigationMock, canGoNext: undefined }, + }); + + expect(screen.getByText('Next')).not.toBeDisabled(); + }); + + it('enables the previous button when canGoPrevious is undefined', () => { + renderStudioPaginatedContent({ + navigation: { ...navigationMock, canGoPrevious: undefined }, + }); + + expect(screen.getByText('Next')).not.toBeDisabled(); + }); + + it('disables the next button when canGoNext is false', () => { + renderStudioPaginatedContent({ + navigation: { ...navigationMock, canGoNext: false }, + }); + + expect(screen.getByText('Next')).toBeDisabled(); + }); + + it('calls onPrevious when the previous button is clicked', async () => { + const user = userEvent.setup(); + renderStudioPaginatedContent(); + + await user.click(screen.getByText('Previous')); + expect(defaultProps.navigation.onPrevious).toHaveBeenCalled(); + }); + + it('calls onNext when the next button is clicked', async () => { + const user = userEvent.setup(); + renderStudioPaginatedContent(); + + await user.click(screen.getByText('Next')); + expect(defaultProps.navigation.onNext).toHaveBeenCalled(); + }); + + it('highlights the correct navigation circle based on currentPageNumber', () => { + renderStudioPaginatedContent(); + const activeCircles = screen + .getAllByRole('status') + .filter((circle) => circle.classList.contains('active')); + expect(activeCircles.length).toBe(defaultProps.currentPageNumber + 1); + }); +}); + +const renderStudioPaginatedContent = (props: Partial<StudioPaginatedContentProps> = {}) => { + return render(<StudioPaginatedContent {...defaultProps} {...props} />); +}; diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.tsx b/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.tsx new file mode 100644 index 00000000000..2b4b56ee83b --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/StudioPaginatedContent.tsx @@ -0,0 +1,68 @@ +import React, { type ReactNode, type ReactElement } from 'react'; +import classes from './StudioPaginatedContent.module.css'; +import { StudioButton } from '../StudioButton'; +import { ChevronLeftIcon, ChevronRightIcon } from '@studio/icons'; +import { type StudioPaginatedNavigation } from './types/StudioPaginatedNavigation'; + +type NavigationButtonTexts = { + previous: string; + next: string; +}; + +export type StudioPaginatedContentProps = { + totalPages: number; + currentPageNumber: number; + componentToRender: ReactNode; + navigationButtonTexts: NavigationButtonTexts; + navigation: StudioPaginatedNavigation; +}; + +export const StudioPaginatedContent = ({ + navigation: { canGoNext = true, canGoPrevious = true, onNext, onPrevious }, + totalPages, + componentToRender, + currentPageNumber, + navigationButtonTexts: { previous: previousButtonText, next: nextButtonText }, +}: StudioPaginatedContentProps): ReactElement => { + return ( + <div className={classes.wrapper}> + <div>{componentToRender}</div> + <div className={classes.buttonWrapper}> + <StudioButton variant='tertiary' size='sm' onClick={onPrevious} disabled={!canGoPrevious}> + <ChevronLeftIcon className={classes.icon} /> + {previousButtonText} + </StudioButton> + <NavigationStepIndicator totalPages={totalPages} currentPageNumber={currentPageNumber} /> + <StudioButton variant='tertiary' size='sm' onClick={onNext} disabled={!canGoNext}> + {nextButtonText} + <ChevronRightIcon /> + </StudioButton> + </div> + </div> + ); +}; + +type NavigationCirclesProps = { + totalPages: number; + currentPageNumber: number; +}; + +const NavigationStepIndicator = ({ + totalPages, + currentPageNumber, +}: NavigationCirclesProps): React.ReactElement => { + return ( + <div className={classes.statusBarContainer}> + {getArrayFromLength(totalPages).map((_, index) => ( + <div + key={index} + role='status' + className={`${classes.statusBarPiece} ${index <= currentPageNumber ? classes.active : ''}`} + /> + ))} + </div> + ); +}; + +const getArrayFromLength = (length: number): number[] => + Array.from({ length }, (_, index) => index); diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/hooks/usePagination.test.tsx b/frontend/libs/studio-components/src/components/StudioPaginatedContent/hooks/usePagination.test.tsx new file mode 100644 index 00000000000..6ad78bfc8f4 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/hooks/usePagination.test.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { renderHook, act } from '@testing-library/react'; +import { usePagination } from './usePagination'; +import { type StudioPaginatedItem } from '../types/StudioPaginatedItem'; + +const items: StudioPaginatedItem[] = [ + { pageContent: <div>Page 1</div>, validationRuleForNextButton: true }, + { pageContent: <div>Page 2</div>, validationRuleForNextButton: true }, + { pageContent: <div>Page 3</div>, validationRuleForNextButton: false }, +]; + +describe('usePagination', () => { + it('should initialize with the first page', () => { + const { result } = renderHook(() => usePagination(items)); + expect(result.current.currentPage).toBe(0); + expect(result.current.pages).toHaveLength(3); + expect(result.current.navigation.canGoNext).toBe(true); + expect(result.current.navigation.canGoPrevious).toBe(false); + }); + + it('should set canGoNext to true when validationRuleForNextButton is undefined', () => { + const itemsWithoutValidationRuleForNextButton: StudioPaginatedItem[] = [ + { pageContent: <div>Page 1</div> }, + { pageContent: <div>Page 2</div> }, + ]; + const { result } = renderHook(() => usePagination(itemsWithoutValidationRuleForNextButton)); + + expect(result.current.currentPage).toBe(0); + expect(result.current.pages).toHaveLength(2); + expect(result.current.navigation.canGoNext).toBe(true); + expect(result.current.navigation.canGoPrevious).toBe(false); + }); + + it('should go to the next page if validation rule allows', () => { + const { result } = renderHook(() => usePagination(items)); + act(() => { + result.current.navigation.onNext(); + }); + expect(result.current.currentPage).toBe(1); + expect(result.current.navigation.canGoNext).toBe(true); + expect(result.current.navigation.canGoPrevious).toBe(true); + }); + + it('should not go to the next page if validation rule does not allow', () => { + const { result } = renderHook(() => usePagination(items)); + act(() => { + result.current.navigation.onNext(); + result.current.navigation.onNext(); + }); + expect(result.current.currentPage).toBe(2); + expect(result.current.navigation.canGoNext).toBe(false); + expect(result.current.navigation.canGoPrevious).toBe(true); + }); + + it('should go to the previous page', () => { + const { result } = renderHook(() => usePagination(items)); + act(() => { + result.current.navigation.onNext(); + }); + expect(result.current.currentPage).toBe(1); + + act(() => { + result.current.navigation.onPrevious(); + }); + expect(result.current.currentPage).toBe(0); + + expect(result.current.navigation.canGoNext).toBe(true); + expect(result.current.navigation.canGoPrevious).toBe(false); + }); + + it('should not go to the previous page if already on the first page', () => { + const { result } = renderHook(() => usePagination(items)); + act(() => { + result.current.navigation.onPrevious(); + }); + expect(result.current.currentPage).toBe(0); + expect(result.current.navigation.canGoNext).toBe(true); + expect(result.current.navigation.canGoPrevious).toBe(false); + }); +}); diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/hooks/usePagination.ts b/frontend/libs/studio-components/src/components/StudioPaginatedContent/hooks/usePagination.ts new file mode 100644 index 00000000000..a56fd684578 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/hooks/usePagination.ts @@ -0,0 +1,44 @@ +import { type ReactNode, useState } from 'react'; +import { type StudioPaginatedNavigation } from '../types/StudioPaginatedNavigation'; +import { type StudioPaginatedItem } from '../types/StudioPaginatedItem'; + +export const usePagination = (items: StudioPaginatedItem[]) => { + const [currentPage, setCurrentPage] = useState<number>(0); + + const hasPreviousPage: boolean = currentPage > 0; + const hasNextPage: boolean = currentPage < items.length - 1; + + const validationRules: boolean[] = mapItemsToValidationRules(items); + const pages: ReactNode[] = mapItemsToPages(items); + + const canGoToNextPage: boolean = validationRules[currentPage] && hasNextPage; + + const goNext = () => { + if (canGoToNextPage) { + setCurrentPage((current: number) => current + 1); + } + }; + + const goPrevious = () => { + if (hasPreviousPage) { + setCurrentPage((current: number) => current - 1); + } + }; + + const navigation: StudioPaginatedNavigation = { + canGoNext: canGoToNextPage, + canGoPrevious: hasPreviousPage, + onNext: goNext, + onPrevious: goPrevious, + }; + + return { currentPage, pages, navigation }; +}; + +const mapItemsToValidationRules = (items: StudioPaginatedItem[]): boolean[] => { + return items.map((item: StudioPaginatedItem) => item?.validationRuleForNextButton ?? true); +}; + +const mapItemsToPages = (items: StudioPaginatedItem[]): ReactNode[] => { + return items.map((item: StudioPaginatedItem) => item.pageContent); +}; diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/index.ts b/frontend/libs/studio-components/src/components/StudioPaginatedContent/index.ts new file mode 100644 index 00000000000..fbbce9d3949 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/index.ts @@ -0,0 +1 @@ +export { StudioPaginatedContent } from './StudioPaginatedContent'; diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/types/StudioPaginatedItem.ts b/frontend/libs/studio-components/src/components/StudioPaginatedContent/types/StudioPaginatedItem.ts new file mode 100644 index 00000000000..da7815d2402 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/types/StudioPaginatedItem.ts @@ -0,0 +1,6 @@ +import { type ReactNode } from 'react'; + +export type StudioPaginatedItem = { + pageContent: ReactNode; + validationRuleForNextButton?: boolean; +}; diff --git a/frontend/libs/studio-components/src/components/StudioPaginatedContent/types/StudioPaginatedNavigation.ts b/frontend/libs/studio-components/src/components/StudioPaginatedContent/types/StudioPaginatedNavigation.ts new file mode 100644 index 00000000000..d46b1035826 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioPaginatedContent/types/StudioPaginatedNavigation.ts @@ -0,0 +1,6 @@ +export type StudioPaginatedNavigation = { + canGoNext?: boolean; + canGoPrevious?: boolean; + onNext: () => void; + onPrevious: () => void; +}; diff --git a/frontend/libs/studio-components/src/components/StudioProperty/StudioPropertyButton/StudioPropertyButton.module.css b/frontend/libs/studio-components/src/components/StudioProperty/StudioPropertyButton/StudioPropertyButton.module.css index 8932f2d1402..2fe288f90e1 100644 --- a/frontend/libs/studio-components/src/components/StudioProperty/StudioPropertyButton/StudioPropertyButton.module.css +++ b/frontend/libs/studio-components/src/components/StudioProperty/StudioPropertyButton/StudioPropertyButton.module.css @@ -1,6 +1,7 @@ .propertyButton { border-radius: 0; - display: flex; + display: grid; + grid-template-columns: 1fr auto; justify-content: flex-start; margin: calc(-1 * var(--studio-property-button-vertical-spacing)) 0; overflow: hidden; @@ -12,6 +13,12 @@ text-align: left; } +.propertyButton .property { + display: flex; + align-items: center; + gap: var(--fds-spacing-1); +} + .propertyButton.withValue .property { font-weight: 500; } diff --git a/frontend/libs/studio-components/src/components/StudioProperty/StudioPropertyButton/StudioPropertyButton.tsx b/frontend/libs/studio-components/src/components/StudioProperty/StudioPropertyButton/StudioPropertyButton.tsx index 24062ddeae8..37c5f0b2583 100644 --- a/frontend/libs/studio-components/src/components/StudioProperty/StudioPropertyButton/StudioPropertyButton.tsx +++ b/frontend/libs/studio-components/src/components/StudioProperty/StudioPropertyButton/StudioPropertyButton.tsx @@ -51,14 +51,16 @@ const StudioPropertyButton = forwardRef<HTMLButtonElement, StudioPropertyButtonP aria-readonly={readOnly ? true : null} className={className} fullWidth - icon={icon} ref={ref} title={property} variant='tertiary' {...rest} > <span className={classes.content}> - <span className={classes.property}>{property}</span> + <span className={classes.property}> + {icon} + {property} + </span> <span className={classes.value}>{value}</span> </span> {readOnly ? ( diff --git a/frontend/libs/studio-components/src/components/StudioSearch/StudioSearch.module.css b/frontend/libs/studio-components/src/components/StudioSearch/StudioSearch.module.css new file mode 100644 index 00000000000..5f82e293b5f --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioSearch/StudioSearch.module.css @@ -0,0 +1,3 @@ +.studioSearch div:empty { + display: contents; +} diff --git a/frontend/libs/studio-components/src/components/StudioSearch/StudioSearch.tsx b/frontend/libs/studio-components/src/components/StudioSearch/StudioSearch.tsx index 4277f2428e7..ad258af5fe0 100644 --- a/frontend/libs/studio-components/src/components/StudioSearch/StudioSearch.tsx +++ b/frontend/libs/studio-components/src/components/StudioSearch/StudioSearch.tsx @@ -1,6 +1,7 @@ import React, { forwardRef, useId } from 'react'; import { Label, Search, type SearchProps } from '@digdir/designsystemet-react'; import type { WithoutAsChild } from '../../types/WithoutAsChild'; +import classes from './StudioSearch.module.css'; export type StudioSearchProps = WithoutAsChild<SearchProps>; @@ -10,6 +11,7 @@ const StudioSearch = forwardRef<HTMLInputElement, StudioSearchProps>( const searchId = id ?? generatedId; const showLabel = !!label; + // TODO: Remove studioSearch css class when Design System is updated. See issue: https://github.com/digdir/designsystemet/issues/2765 return ( <div className={className}> {showLabel && ( @@ -17,7 +19,7 @@ const StudioSearch = forwardRef<HTMLInputElement, StudioSearchProps>( {label} </Label> )} - <Search {...rest} id={searchId} size={size} ref={ref} /> + <Search {...rest} className={classes.studioSearch} id={searchId} size={size} ref={ref} /> </div> ); }, diff --git a/frontend/libs/studio-content-library/mocks/mockPagesConfig.ts b/frontend/libs/studio-content-library/mocks/mockPagesConfig.ts index 678f509b8ee..801f63b3dda 100644 --- a/frontend/libs/studio-content-library/mocks/mockPagesConfig.ts +++ b/frontend/libs/studio-content-library/mocks/mockPagesConfig.ts @@ -12,6 +12,7 @@ export const mockPagesConfig: PagesConfig = { codeList: { props: { codeListsData: codeListsDataMock, + onDeleteCodeList: () => {}, onUpdateCodeListId: () => {}, onUpdateCodeList: () => {}, onUploadCodeList: () => {}, diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx index e02e6d3ced9..206221027a1 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import type { CodeListPageProps } from './CodeListPage'; +import type { CodeListData, CodeListPageProps } from './CodeListPage'; import { CodeListPage } from './CodeListPage'; import userEvent from '@testing-library/user-event'; import type { UserEvent } from '@testing-library/user-event'; @@ -8,6 +8,7 @@ import { textMock } from '@studio/testing/mocks/i18nMock'; import type { CodeList as StudioComponentCodeList } from '@studio/components'; import { codeListsDataMock } from '../../../../../mocks/mockPagesConfig'; +const onDeleteCodeListMock = jest.fn(); const onUpdateCodeListIdMock = jest.fn(); const onUpdateCodeListMock = jest.fn(); const onUploadCodeListMock = jest.fn(); @@ -59,14 +60,43 @@ describe('CodeListPage', () => { it('renders the code list accordion', () => { renderCodeListPage(); - const codeListAccordion = screen.getByTitle( - textMock('app_content_library.code_lists.code_list_accordion_title', { - codeListTitle: codeListName, - }), - ); + const codeListAccordion = getCodeListAccordion(codeListName); expect(codeListAccordion).toBeInTheDocument(); }); + it('renders all code lists when search param matches all lists', async () => { + const user = userEvent.setup(); + const codeList2 = 'codeList2'; + const codeListsSearchParam = 'code'; + renderCodeListPage({ + codeListsData: [...codeListsDataMock, { title: codeList2, data: codeListMock }], + }); + const searchInput = screen.getByRole('searchbox'); + await user.type(searchInput, codeListsSearchParam); + [codeListName, codeList2].forEach((codeListTitle) => { + expect(getCodeListAccordion(codeListTitle)).toBeInTheDocument(); + }); + }); + + it('renders the matching code lists when search param limits result', async () => { + const user = userEvent.setup(); + const codeList2 = 'codeList2'; + const codeListsSearchParam = '2'; + renderCodeListPage({ + codeListsData: [...codeListsDataMock, { title: codeList2, data: codeListMock }], + }); + const searchInput = screen.getByRole('searchbox'); + await user.type(searchInput, codeListsSearchParam); + expect(getCodeListAccordion(codeList2)).toBeInTheDocument(); + expect( + screen.queryByTitle( + textMock('app_content_library.code_lists.code_list_accordion_title', { + codeListTitle: codeListName, + }), + ), + ).not.toBeInTheDocument(); + }); + it('render the code list accordion as default open when uploading a code list', async () => { const user = userEvent.setup(); const { rerender } = renderCodeListPage(); @@ -80,11 +110,13 @@ describe('CodeListPage', () => { title: uploadedCodeListName, data: codeListMock, }); - rerender(<CodeListPage {...defaultCodeListPageProps} />); + const newCodeListsData: CodeListData[] = [...defaultCodeListPageProps.codeListsData]; + rerender(<CodeListPage {...defaultCodeListPageProps} codeListsData={newCodeListsData} />); const codeListAccordionOpen = screen.getByRole('button', { name: uploadedCodeListName, expanded: true, }); + expect(codeListAccordionClosed).toHaveAttribute('aria-expanded', 'false'); expect(codeListAccordionOpen).toHaveAttribute('aria-expanded', 'true'); }); @@ -149,8 +181,17 @@ const uploadCodeList = async (user: UserEvent, fileName: string = uploadedCodeLi await user.upload(fileUploaderButton, file); }; +const getCodeListAccordion = (codeListTitle: string) => { + return screen.getByTitle( + textMock('app_content_library.code_lists.code_list_accordion_title', { + codeListTitle, + }), + ); +}; + const defaultCodeListPageProps: CodeListPageProps = { codeListsData: codeListsDataMock, + onDeleteCodeList: onDeleteCodeListMock, onUpdateCodeListId: onUpdateCodeListIdMock, onUpdateCodeList: onUpdateCodeListMock, onUploadCodeList: onUploadCodeListMock, diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx index 9c93970f191..250b95ac833 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { StudioHeading } from '@studio/components'; import type { CodeList } from '@studio/components'; import { useTranslation } from 'react-i18next'; @@ -8,6 +8,7 @@ import { CodeListsCounterMessage } from './CodeListsCounterMessage'; import classes from './CodeListPage.module.css'; import { ArrayUtils, FileNameUtils } from '@studio/pure-functions'; import type { CodeListReference } from './types/CodeListReference'; +import { filterCodeLists } from './utils/codeListPageUtils'; export type CodeListWithMetadata = { codeList: CodeList; @@ -22,6 +23,7 @@ export type CodeListData = { export type CodeListPageProps = { codeListsData: CodeListData[]; + onDeleteCodeList: (codeListId: string) => void; onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void; onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void; onUploadCodeList: (uploadedCodeList: File) => void; @@ -30,14 +32,21 @@ export type CodeListPageProps = { export function CodeListPage({ codeListsData, + onDeleteCodeList, onUpdateCodeListId, onUpdateCodeList, onUploadCodeList, codeListsUsages, }: CodeListPageProps): React.ReactElement { const { t } = useTranslation(); + const [searchString, setSearchString] = useState<string>(''); const [codeListInEditMode, setCodeListInEditMode] = useState<string>(undefined); + const filteredCodeLists: CodeListData[] = useMemo( + () => filterCodeLists(codeListsData, searchString), + [codeListsData, searchString], + ); + const codeListTitles = ArrayUtils.mapByKey<CodeListData, 'title'>(codeListsData, 'title'); const handleUploadCodeList = (uploadedCodeList: File) => { @@ -58,9 +67,11 @@ export function CodeListPage({ onUploadCodeList={handleUploadCodeList} onUpdateCodeList={onUpdateCodeList} codeListNames={codeListTitles} + onSetSearchString={setSearchString} /> <CodeLists - codeListsData={codeListsData} + codeListsData={filteredCodeLists} + onDeleteCodeList={onDeleteCodeList} onUpdateCodeListId={handleUpdateCodeListId} onUpdateCodeList={onUpdateCodeList} codeListInEditMode={codeListInEditMode} diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx index dae9ec96200..9753a136497 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx @@ -12,6 +12,7 @@ import type { CodeList as StudioComponentsCodeList } from '@studio/components'; import { codeListsDataMock } from '../../../../../../mocks/mockPagesConfig'; const codeListName = codeListsDataMock[0].title; +const onDeleteCodeListMock = jest.fn(); const onUpdateCodeListIdMock = jest.fn(); const onUpdateCodeListMock = jest.fn(); @@ -137,6 +138,26 @@ describe('CodeLists', () => { expect(codeListUsagesModalTitle).toBeInTheDocument(); }); + it('renders button to delete code list as disabled when code list is used', async () => { + renderCodeLists({ + codeListsUsages: [ + { + codeListId: codeListName, + codeListIdSources: [ + { layoutSetId: 'layoutSetId', layoutName: 'layoutName', componentIds: ['componentId'] }, + ], + }, + ], + }); + const deleteCodeListButton = screen.getByRole('button', { + name: textMock('app_content_library.code_lists.code_list_delete'), + }); + expect(deleteCodeListButton).toBeDisabled(); + expect(deleteCodeListButton.title).toBe( + textMock('app_content_library.code_lists.code_list_delete_disabled_title'), + ); + }); + it('renders the code list editor', () => { renderCodeLists(); const codeListEditor = screen.getByText(textMock('code_list_editor.legend')); @@ -176,6 +197,22 @@ describe('CodeLists', () => { expect(onUpdateCodeListIdMock).toHaveBeenLastCalledWith(codeListName, codeListName + '2'); }); + it('renders display tile instead of edit button when the code list is in use', async () => { + renderCodeLists({ + codeListsUsages: [ + { + codeListId: codeListsDataMock[0].title, + codeListIdSources: [{ layoutSetId: '', layoutName: '', componentIds: [''] }], + }, + ], + }); + const codeListId = screen.getByTitle( + textMock('app_content_library.code_lists.code_list_edit_id_disabled_title'), + ); + expect(codeListId).toBeInTheDocument(); + expect(codeListId).not.toHaveAttribute('role', 'button'); + }); + it('shows error message when assigning an invalid id to the code list', async () => { const user = userEvent.setup(); const invalidCodeListName = 'invalidCodeListName'; @@ -206,6 +243,20 @@ describe('CodeLists', () => { const errorMessage = screen.getByText(textMock('app_content_library.code_lists.fetch_error')); expect(errorMessage).toBeInTheDocument(); }); + + it('calls onDeleteCodeList when clicking delete button', async () => { + const user = userEvent.setup(); + renderCodeLists(); + const deleteCodeListButton = screen.getByRole('button', { + name: textMock('app_content_library.code_lists.code_list_delete'), + }); + expect(deleteCodeListButton.title).toBe( + textMock('app_content_library.code_lists.code_list_delete_enabled_title'), + ); + await user.click(deleteCodeListButton); + expect(onDeleteCodeListMock).toHaveBeenCalledTimes(1); + expect(onDeleteCodeListMock).toHaveBeenLastCalledWith(codeListName); + }); }); const changeCodeListId = async (user: UserEvent, oldCodeListId: string, newCodeListId: string) => { @@ -227,6 +278,7 @@ const changeCodeListId = async (user: UserEvent, oldCodeListId: string, newCodeL const defaultProps: CodeListsProps = { codeListsData: codeListsDataMock, + onDeleteCodeList: onDeleteCodeListMock, onUpdateCodeListId: onUpdateCodeListIdMock, onUpdateCodeList: onUpdateCodeListMock, codeListInEditMode: undefined, diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx index 67ae8b95eeb..f37940e9653 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx @@ -6,10 +6,11 @@ import { EditCodeList } from './EditCodeList/EditCodeList'; import { useTranslation } from 'react-i18next'; import type { CodeListIdSource, CodeListReference } from '../types/CodeListReference'; import classes from './CodeLists.module.css'; -import { getCodeListSourcesById, getCodeListUsageCount } from '../utils'; +import { getCodeListSourcesById, getCodeListUsageCount } from '../utils/codeListPageUtils'; export type CodeListsProps = { codeListsData: CodeListData[]; + onDeleteCodeList: (codeListId: string) => void; onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void; onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void; codeListInEditMode: string | undefined; @@ -19,11 +20,8 @@ export type CodeListsProps = { export function CodeLists({ codeListsData, - onUpdateCodeListId, - onUpdateCodeList, - codeListInEditMode, - codeListNames, codeListsUsages, + ...rest }: CodeListsProps): React.ReactElement[] { return codeListsData.map((codeListData) => { const codeListSources = getCodeListSourcesById(codeListsUsages, codeListData.title); @@ -31,10 +29,7 @@ export function CodeLists({ <CodeList key={codeListData.title} codeListData={codeListData} - onUpdateCodeListId={onUpdateCodeListId} - onUpdateCodeList={onUpdateCodeList} - codeListInEditMode={codeListInEditMode} - codeListNames={codeListNames} + {...rest} codeListSources={codeListSources} /> ); @@ -48,11 +43,9 @@ type CodeListProps = Omit<CodeListsProps, 'codeListsData' | 'codeListsUsages'> & function CodeList({ codeListData, - onUpdateCodeListId, - onUpdateCodeList, codeListInEditMode, - codeListNames, codeListSources, + ...rest }: CodeListProps): React.ReactElement { return ( <Accordion border> @@ -63,10 +56,8 @@ function CodeList({ /> <CodeListAccordionContent codeListData={codeListData} - onUpdateCodeListId={onUpdateCodeListId} - onUpdateCodeList={onUpdateCodeList} - codeListNames={codeListNames} codeListSources={codeListSources} + {...rest} /> </Accordion.Item> </Accordion> @@ -120,10 +111,8 @@ type CodeListAccordionContentProps = Omit<CodeListProps, 'codeListInEditMode'>; function CodeListAccordionContent({ codeListData, - onUpdateCodeListId, - onUpdateCodeList, - codeListNames, codeListSources, + ...rest }: CodeListAccordionContentProps): React.ReactElement { const { t } = useTranslation(); @@ -137,10 +126,8 @@ function CodeListAccordionContent({ <EditCodeList codeList={codeListData.data} codeListTitle={codeListData.title} - onUpdateCodeListId={onUpdateCodeListId} - onUpdateCodeList={onUpdateCodeList} - codeListNames={codeListNames} codeListSources={codeListSources} + {...rest} /> )} </Accordion.Content> diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.module.css b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.module.css index 394b22669bd..06431b07fa7 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.module.css +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.module.css @@ -1,7 +1,7 @@ .editCodeList { display: flex; flex-direction: column; - gap: var(--fds-spacing-2); + gap: var(--fds-spacing-3); } .codeListUsageButton { @@ -11,3 +11,9 @@ .seeUsageIcon { font-size: var(--fds-sizing-5); } + +.buttons { + display: flex; + flex-direction: row; + gap: var(--fds-spacing-3); +} diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx index 0c6b1bffd00..cb7a7539f13 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx @@ -1,9 +1,11 @@ -import type { - CodeList as StudioComponentsCodeList, - CodeList, - CodeListEditorTexts, +import type { CodeList, CodeListEditorTexts } from '@studio/components'; +import { + StudioDeleteButton, + StudioModal, + StudioDisplayTile, + StudioCodeListEditor, + StudioToggleableTextfield, } from '@studio/components'; -import { StudioModal, StudioCodeListEditor, StudioToggleableTextfield } from '@studio/components'; import React from 'react'; import { useTranslation } from 'react-i18next'; import type { CodeListWithMetadata } from '../../CodeListPage'; @@ -18,6 +20,7 @@ import { CodeListUsages } from './CodeListUsages/CodeListUsages'; export type EditCodeListProps = { codeList: CodeList; codeListTitle: string; + onDeleteCodeList: (codeListId: string) => void; onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void; onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void; codeListNames: string[]; @@ -27,18 +30,13 @@ export type EditCodeListProps = { export function EditCodeList({ codeList, codeListTitle, + onDeleteCodeList, onUpdateCodeListId, onUpdateCodeList, codeListNames, codeListSources, }: EditCodeListProps): React.ReactElement { - const { t } = useTranslation(); const editorTexts: CodeListEditorTexts = useCodeListEditorTexts(); - const getInvalidInputFileNameErrorMessage = useInputCodeListNameErrorMessage(); - - const handleUpdateCodeListId = (newCodeListId: string) => { - if (newCodeListId !== codeListTitle) onUpdateCodeListId(codeListTitle, newCodeListId); - }; const handleCodeListChange = (updatedCodeList: CodeList): void => { const updatedCodeListWithMetadata = updateCodeListWithMetadata( @@ -48,36 +46,18 @@ export function EditCodeList({ onUpdateCodeList(updatedCodeListWithMetadata); }; - const handleValidateCodeListId = (newCodeListId: string) => { - const invalidCodeListNames = ArrayUtils.removeItemByValue(codeListNames, codeListTitle); - const fileNameError = FileNameUtils.findFileNameError(newCodeListId, invalidCodeListNames); - return getInvalidInputFileNameErrorMessage(fileNameError); - }; + const handleDeleteCodeList = (): void => onDeleteCodeList(codeListTitle); const codeListHasUsages = codeListSources.length > 0; + const isCodeListEditable = codeListSources.length === 0; return ( <div className={classes.editCodeList}> - <StudioToggleableTextfield - customValidation={handleValidateCodeListId} - inputProps={{ - label: t('app_content_library.code_lists.code_list_edit_id_label'), - icon: <KeyVerticalIcon />, - title: t('app_content_library.code_lists.code_list_edit_id_title', { - codeListName: codeListTitle, - }), - value: codeListTitle, - onBlur: (event) => handleUpdateCodeListId(event.target.value), - size: 'small', - }} - viewProps={{ - label: t('app_content_library.code_lists.code_list_edit_id_label'), - children: codeListTitle, - variant: 'tertiary', - title: t('app_content_library.code_lists.code_list_view_id_title', { - codeListName: codeListTitle, - }), - }} + <EditCodeListTitle + codeListTitle={codeListTitle} + isCodeListEditable={isCodeListEditable} + codeListNames={codeListNames} + onUpdateCodeListId={onUpdateCodeListId} /> <StudioCodeListEditor codeList={codeList} @@ -85,18 +65,110 @@ export function EditCodeList({ onBlurAny={handleCodeListChange} texts={editorTexts} /> - {codeListHasUsages && <ShowCodeListUsagesSourcesModal codeListSources={codeListSources} />} + <CodeListButtons + codeListHasUsages={codeListHasUsages} + codeListSources={codeListSources} + onDeleteCodeList={handleDeleteCodeList} + /> </div> ); } export const updateCodeListWithMetadata = ( currentCodeListWithMetadata: CodeListWithMetadata, - updatedCodeList: StudioComponentsCodeList, + updatedCodeList: CodeList, ): CodeListWithMetadata => { return { ...currentCodeListWithMetadata, codeList: updatedCodeList }; }; +type EditCodeListTitleProps = { + codeListTitle: string; + isCodeListEditable: boolean; + codeListNames: string[]; + onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void; +}; + +function EditCodeListTitle({ + codeListTitle, + isCodeListEditable, + codeListNames, + onUpdateCodeListId, +}: EditCodeListTitleProps): React.ReactElement { + const { t } = useTranslation(); + const getInvalidInputFileNameErrorMessage = useInputCodeListNameErrorMessage(); + + const handleUpdateCodeListId = (newCodeListId: string) => { + if (newCodeListId !== codeListTitle) onUpdateCodeListId(codeListTitle, newCodeListId); + }; + + const handleValidateCodeListId = (newCodeListId: string) => { + const invalidCodeListNames = ArrayUtils.removeItemByValue(codeListNames, codeListTitle); + const fileNameError = FileNameUtils.findFileNameError(newCodeListId, invalidCodeListNames); + return getInvalidInputFileNameErrorMessage(fileNameError); + }; + + return isCodeListEditable ? ( + <StudioToggleableTextfield + customValidation={handleValidateCodeListId} + inputProps={{ + label: t('app_content_library.code_lists.code_list_edit_id_label'), + icon: <KeyVerticalIcon />, + title: t('app_content_library.code_lists.code_list_edit_id_title', { + codeListName: codeListTitle, + }), + value: codeListTitle, + onBlur: (event) => handleUpdateCodeListId(event.target.value), + size: 'small', + }} + viewProps={{ + label: t('app_content_library.code_lists.code_list_edit_id_label'), + children: codeListTitle, + variant: 'tertiary', + title: t('app_content_library.code_lists.code_list_view_id_title', { + codeListName: codeListTitle, + }), + }} + /> + ) : ( + <StudioDisplayTile + title={t('app_content_library.code_lists.code_list_edit_id_disabled_title')} + label={t('app_content_library.code_lists.code_list_edit_id_label')} + value={codeListTitle} + icon={<KeyVerticalIcon />} + /> + ); +} + +type CodeListButtonsProps = { + codeListHasUsages: boolean; + codeListSources: CodeListIdSource[]; + onDeleteCodeList: (codeListId: string) => void; +}; + +function CodeListButtons({ + codeListHasUsages, + codeListSources, + onDeleteCodeList, +}: CodeListButtonsProps): React.ReactElement { + const { t } = useTranslation(); + const deleteButtonTitle = codeListHasUsages + ? t('app_content_library.code_lists.code_list_delete_disabled_title') + : t('app_content_library.code_lists.code_list_delete_enabled_title'); + + return ( + <div className={classes.buttons}> + <StudioDeleteButton + onDelete={onDeleteCodeList} + title={deleteButtonTitle} + disabled={codeListHasUsages} + > + {t('app_content_library.code_lists.code_list_delete')} + </StudioDeleteButton> + {codeListHasUsages && <ShowCodeListUsagesSourcesModal codeListSources={codeListSources} />} + </div> + ); +} + export type ShowCodeListUsagesSourcesModalProps = { codeListSources: CodeListIdSource[]; }; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.module.css b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.module.css index 434dcd00378..d6d468dbae4 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.module.css +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.module.css @@ -5,7 +5,3 @@ gap: var(--fds-spacing-3); justify-content: flex-start; } - -.searchField { - display: flex; -} diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.test.tsx index a8ab7c7ee8e..1fef98b3ec3 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.test.tsx @@ -1,12 +1,14 @@ import React from 'react'; import { screen } from '@testing-library/react'; import { textMock } from '@studio/testing/mocks/i18nMock'; +import type { CodeListsActionsBarProps } from './CodeListsActionsBar'; import { CodeListsActionsBar } from './CodeListsActionsBar'; import type { UserEvent } from '@testing-library/user-event'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '../../../../../../test-utils/renderWithProviders'; const onUploadCodeListMock = jest.fn(); +const onSetSearchStringMock = jest.fn(); const codeListName1 = 'codeListName1'; const codeListName2 = 'codeListName2'; @@ -21,6 +23,30 @@ describe('CodeListsActionsBar', () => { expect(searchFieldLabelText).toBeInTheDocument(); }); + it('calls onSetCodeListSearchPatternMock when searching for code lists', async () => { + const user = userEvent.setup(); + renderCodeListsActionsBar(); + const searchInput = screen.getByRole('searchbox'); + const codeListSearchParam = 'code'; + await user.type(searchInput, codeListSearchParam); + expect(onSetSearchStringMock).toHaveBeenCalledTimes(codeListSearchParam.length); + expect(onSetSearchStringMock).toHaveBeenCalledWith(codeListSearchParam); + }); + + it('calls onSetCodeListSearchPatternMock with empty string when clearing search', async () => { + const user = userEvent.setup(); + renderCodeListsActionsBar(); + const searchInput = screen.getByRole('searchbox'); + const codeListSearchParam = 'code'; + await user.type(searchInput, codeListSearchParam); + const clearSearchButton = screen.getByRole('button', { + name: textMock('app_content_library.code_lists.clear_search_button_label'), + }); + await user.click(clearSearchButton); + expect(onSetSearchStringMock).toHaveBeenCalledTimes(codeListSearchParam.length + 1); // +1 due to clearing search + expect(onSetSearchStringMock).toHaveBeenLastCalledWith(''); + }); + it('renders the file uploader button', () => { renderCodeListsActionsBar(); const fileUploaderButton = screen.getByLabelText( @@ -98,12 +124,13 @@ const uploadFileWithFileName = async (user: UserEvent, fileNameWithExtension: st await user.upload(fileUploaderButton, file); }; +const defaultCodeListActionBarProps: CodeListsActionsBarProps = { + onUploadCodeList: onUploadCodeListMock, + onUpdateCodeList: jest.fn(), + codeListNames: [codeListName1, codeListName2], + onSetSearchString: onSetSearchStringMock, +}; + const renderCodeListsActionsBar = () => { - renderWithProviders( - <CodeListsActionsBar - onUploadCodeList={onUploadCodeListMock} - onUpdateCodeList={jest.fn()} - codeListNames={[codeListName1, codeListName2]} - />, - ); + return renderWithProviders(<CodeListsActionsBar {...defaultCodeListActionBarProps} />); }; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.tsx index 8c87f1004e0..e827fddc4af 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { StudioFileUploader, StudioSearch } from '@studio/components'; +import type { ChangeEvent } from 'react'; import classes from './CodeListsActionsBar.module.css'; import { useTranslation } from 'react-i18next'; import type { CodeListWithMetadata } from '../CodeListPage'; @@ -8,20 +9,27 @@ import { FileNameUtils } from '@studio/pure-functions'; import { useUploadCodeListNameErrorMessage } from '../hooks/useUploadCodeListNameErrorMessage'; import { toast } from 'react-toastify'; -type CodeListsActionsBarProps = { +export type CodeListsActionsBarProps = { onUploadCodeList: (updatedCodeList: File) => void; onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void; codeListNames: string[]; + onSetSearchString: (searchString: string) => void; }; export function CodeListsActionsBar({ onUploadCodeList, onUpdateCodeList, codeListNames, + onSetSearchString, }: CodeListsActionsBarProps) { const { t } = useTranslation(); const getInvalidUploadFileNameErrorMessage = useUploadCodeListNameErrorMessage(); + const handleChangeSearch = (event: ChangeEvent<HTMLInputElement>) => + onSetSearchString(event.target.value); + + const handleClearSearch = () => onSetSearchString(''); + const onSubmit = (file: File) => { const fileNameError = FileNameUtils.findFileNameError( FileNameUtils.removeExtension(file.name), @@ -36,8 +44,10 @@ export function CodeListsActionsBar({ return ( <div className={classes.actionsBar}> <StudioSearch - className={classes.searchField} label={t('app_content_library.code_lists.search_label')} + onChange={handleChangeSearch} + clearButtonLabel={t('app_content_library.code_lists.clear_search_button_label')} + onClear={handleClearSearch} /> <CreateNewCodeListModal onUpdateCodeList={onUpdateCodeList} codeListNames={codeListNames} /> <StudioFileUploader diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CreateNewCodeListModal/CreateNewCodeListModal.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CreateNewCodeListModal/CreateNewCodeListModal.test.tsx index efe11b48ef3..e03a66b758e 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CreateNewCodeListModal/CreateNewCodeListModal.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CreateNewCodeListModal/CreateNewCodeListModal.test.tsx @@ -83,7 +83,7 @@ describe('CreateNewCodeListModal', () => { renderCreateNewCodeListModal(); await openDialog(user); await inputCodeListTitle(user, 'æ'); - const codeListTitleError = screen.getByText(textMock('validation_errors.file_name_invalid')); + const codeListTitleError = screen.getByText(textMock('validation_errors.name_invalid')); expect(codeListTitleError).toBeInTheDocument(); }); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useCommonCodeListNameErrorMessages.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useCommonCodeListNameErrorMessages.ts deleted file mode 100644 index 489a91eb2be..00000000000 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useCommonCodeListNameErrorMessages.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { FileNameErrorResult } from '@studio/pure-functions'; -import { useTranslation } from 'react-i18next'; - -export type CommonMessageFileNameError = Record<FileNameErrorResult.NoRegExMatch, string>; - -export function useCommonCodeListNameErrorMessages(): CommonMessageFileNameError { - const { t } = useTranslation(); - - return { - [FileNameErrorResult.NoRegExMatch]: t('validation_errors.file_name_invalid'), - }; -} diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useInputCodeListNameErrorMessage.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useInputCodeListNameErrorMessage.ts index ef5d6e0d607..54e30b9d68f 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useInputCodeListNameErrorMessage.ts +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useInputCodeListNameErrorMessage.ts @@ -1,13 +1,10 @@ import { FileNameErrorResult } from '@studio/pure-functions'; -import type { CommonMessageFileNameError } from './useCommonCodeListNameErrorMessages'; -import { useCommonCodeListNameErrorMessages } from './useCommonCodeListNameErrorMessages'; import { useTranslation } from 'react-i18next'; export function useInputCodeListNameErrorMessage(): (fileNameError: FileNameErrorResult) => string { const { t } = useTranslation(); - const commonErrorMessage: CommonMessageFileNameError = useCommonCodeListNameErrorMessages(); const errorMessages: Record<FileNameErrorResult, string> = { - ...commonErrorMessage, + [FileNameErrorResult.NoRegExMatch]: t('validation_errors.name_invalid'), [FileNameErrorResult.FileNameIsEmpty]: t('validation_errors.required'), [FileNameErrorResult.FileExists]: t('validation_errors.file_name_occupied'), }; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useUploadCodeListNameErrorMessage.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useUploadCodeListNameErrorMessage.ts index 912eb670f20..71c25720f9b 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useUploadCodeListNameErrorMessage.ts +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/hooks/useUploadCodeListNameErrorMessage.ts @@ -1,15 +1,12 @@ import { FileNameErrorResult } from '@studio/pure-functions'; -import type { CommonMessageFileNameError } from './useCommonCodeListNameErrorMessages'; -import { useCommonCodeListNameErrorMessages } from './useCommonCodeListNameErrorMessages'; import { useTranslation } from 'react-i18next'; export function useUploadCodeListNameErrorMessage(): ( fileNameError: FileNameErrorResult, ) => string { const { t } = useTranslation(); - const commonErrorMessage: CommonMessageFileNameError = useCommonCodeListNameErrorMessages(); const errorMessages: Record<FileNameErrorResult, string> = { - ...commonErrorMessage, + [FileNameErrorResult.NoRegExMatch]: t('validation_errors.file_name_invalid'), [FileNameErrorResult.FileNameIsEmpty]: t('validation_errors.upload_file_name_required'), [FileNameErrorResult.FileExists]: t('validation_errors.upload_file_name_occupied'), }; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/codeListPageUtils.test.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/codeListPageUtils.test.ts index 347a423fb50..90685b0c6bc 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/codeListPageUtils.test.ts +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/codeListPageUtils.test.ts @@ -1,5 +1,10 @@ import type { CodeListIdSource, CodeListReference } from '../types/CodeListReference'; -import { getCodeListSourcesById, getCodeListUsageCount } from './codeListPageUtils'; +import { + filterCodeLists, + getCodeListSourcesById, + getCodeListUsageCount, +} from './codeListPageUtils'; +import type { CodeListData } from '../CodeListPage'; const codeListId1: string = 'codeListId1'; const codeListId2: string = 'codeListId2'; @@ -86,3 +91,51 @@ describe('getCodeListUsageCount', () => { expect(usageCount).toBe(0); }); }); + +describe('filterCodeLists', () => { + const codeLists: CodeListData[] = [ + { title: 'Fruits' }, + { title: 'Vegetables' }, + { title: 'Dairy Products' }, + { title: 'Frozen Foods' }, + ]; + + it('should return all code lists when the search string is empty', () => { + const result = filterCodeLists(codeLists, ''); + expect(result).toEqual(codeLists); + }); + + it('should filter code lists by exact match', () => { + const result = filterCodeLists(codeLists, 'Fruits'); + expect(result).toEqual([{ title: 'Fruits' }]); + }); + + it('should filter code lists by partial match', () => { + const result = filterCodeLists(codeLists, 'Food'); + expect(result).toEqual([{ title: 'Frozen Foods' }]); + }); + + it('should filter code lists case-insensitively', () => { + const result = filterCodeLists(codeLists, 'fruits'); + expect(result).toEqual([{ title: 'Fruits' }]); + }); + + it('should return an empty array when no matches are found', () => { + const result = filterCodeLists(codeLists, 'Meat'); + expect(result).toEqual([]); + }); + + it('should support an empty code list array', () => { + const result = filterCodeLists([], 'Fruits'); + expect(result).toEqual([]); + }); + + it('should support special characters in search strings', () => { + const specialCharacterCodeLists: CodeListData[] = [ + { title: 'Cakes & Cookies' }, + { title: 'Ice-Cream' }, + ]; + const result = filterCodeLists(specialCharacterCodeLists, '&'); + expect(result).toEqual([{ title: 'Cakes & Cookies' }]); + }); +}); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/codeListPageUtils.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/codeListPageUtils.ts index dedb9780c78..f36ffa5b310 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/codeListPageUtils.ts +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/codeListPageUtils.ts @@ -1,4 +1,5 @@ import type { CodeListIdSource, CodeListReference } from '../types/CodeListReference'; +import type { CodeListData } from '../CodeListPage'; export const getCodeListSourcesById = ( codeListsUsages: CodeListReference[], @@ -16,3 +17,19 @@ export const getCodeListUsageCount = (codeListSources: CodeListIdSource[]): numb 0, ); }; + +export const filterCodeLists = ( + codeListsData: CodeListData[], + searchString: string, +): CodeListData[] => + codeListsData.filter((codeList: CodeListData) => codeListMatch(codeList.title, searchString)); + +function codeListMatch(codeListTitle: string, searchString: string): boolean { + return caseInsensitiveMatch(codeListTitle, searchString); +} + +function caseInsensitiveMatch(target: string, searchString: string): boolean { + const lowerCaseTarget = target.toLowerCase(); + const lowerCaseSearchString = searchString.toLowerCase(); + return lowerCaseTarget.includes(lowerCaseSearchString); +} diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/index.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/index.ts deleted file mode 100644 index c11cad66e14..00000000000 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './codeListPageUtils'; diff --git a/frontend/libs/studio-hooks/package.json b/frontend/libs/studio-hooks/package.json index f65562a40a0..95a4bab9ee2 100644 --- a/frontend/libs/studio-hooks/package.json +++ b/frontend/libs/studio-hooks/package.json @@ -15,13 +15,13 @@ }, "devDependencies": { "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.1.0", + "@testing-library/react": "16.2.0", "@types/jest": "^29.5.5", "eslint": "8.57.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "ts-jest": "^29.1.1", - "typescript": "5.7.2" + "typescript": "5.7.3" }, "peerDependencies": { "react-router-dom": ">=6.0.0" diff --git a/frontend/libs/studio-icons/package.json b/frontend/libs/studio-icons/package.json index 245a1281522..7c45d28fa69 100644 --- a/frontend/libs/studio-icons/package.json +++ b/frontend/libs/studio-icons/package.json @@ -13,11 +13,11 @@ }, "devDependencies": { "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.1.0", + "@testing-library/react": "16.2.0", "@types/jest": "^29.5.5", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "ts-jest": "^29.1.1", - "typescript": "5.7.2" + "typescript": "5.7.3" } } diff --git a/frontend/libs/studio-pure-functions/package.json b/frontend/libs/studio-pure-functions/package.json index 6a44e15e63e..f6087067c7b 100644 --- a/frontend/libs/studio-pure-functions/package.json +++ b/frontend/libs/studio-pure-functions/package.json @@ -13,6 +13,6 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "ts-jest": "^29.1.1", - "typescript": "5.7.2" + "typescript": "5.7.3" } } diff --git a/frontend/packages/policy-editor/package.json b/frontend/packages/policy-editor/package.json index 5dbae7d0863..f37fd2aa1bb 100644 --- a/frontend/packages/policy-editor/package.json +++ b/frontend/packages/policy-editor/package.json @@ -15,6 +15,6 @@ "peerDependencies": { "react": "18.3.1", "react-dom": "18.3.1", - "typescript": "5.7.2" + "typescript": "5.7.3" } } diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/AllAccessPackages.test.tsx b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/AllAccessPackages.test.tsx index d1c192ac896..d1185b820d1 100644 --- a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/AllAccessPackages.test.tsx +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/AllAccessPackages.test.tsx @@ -36,7 +36,7 @@ const groupedAccessPackagesByArea: PolicyAccessPackageArea[] = [ name: area1Name, urn: 'urn:area1', description: '', - icon: 'BankNote', + icon: 'skatt_avgift_regnskap_og_toll', areaGroup: '', packages: [package1, package2], }, @@ -80,6 +80,12 @@ describe('AllAccessPackages', () => { expect(screen.getByText(area1Name)).toBeInTheDocument(); expect(screen.getByText(area2Name)).toBeInTheDocument(); }); + + it('should render default icon if icon is not set', () => { + renderAllAccessPackages(); + + expect(screen.getByTestId('default-icon')).toBeInTheDocument(); + }); }); const renderAllAccessPackages = (props: Partial<AllAccessPackagesProps> = {}) => { diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/AllAccessPackages.tsx b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/AllAccessPackages.tsx index 6ccaca57383..c32950a9a44 100644 --- a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/AllAccessPackages.tsx +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/AllAccessPackages.tsx @@ -5,10 +5,8 @@ import { PolicyAccordion } from '../PolicyAccordion'; import { isAccessPackageSelected } from '../policyAccessPackageUtils'; import type { PolicyAccessPackageArea } from 'app-shared/types/PolicyAccessPackages'; import classes from './AllAccessPackages.module.css'; -// import all icons from StudioIcons. This is because access package area icons are defined in the json -// we load, and we do not know which icons that is (only that the icons are present in StudioIcons). -// this will be changed later in early 2025, when we will use specific icons for access package areas -import * as StudioIcons from '@studio/icons'; +import { PackageIcon } from '@studio/icons'; +import * as Icons from './Icons'; export type AllAccessPackagesProps = { chosenAccessPackages: string[]; @@ -44,8 +42,12 @@ export const AllAccessPackages = ({ type PolicyAccordionIconProps = { icon: string }; const PolicyAccordionIcon = ({ icon }: PolicyAccordionIconProps): ReactElement => { - const IconComponent = Object.keys(StudioIcons).includes(icon) - ? StudioIcons[icon] - : StudioIcons.PackageIcon; - return <IconComponent className={cn(classes.accordionIcon, classes.iconContainer)} aria-hidden />; + const IconComponent = Object.keys(Icons).includes(icon) ? Icons[icon] : PackageIcon; + return ( + <IconComponent + data-testid={icon || 'default-icon'} + className={cn(classes.accordionIcon, classes.iconContainer)} + aria-hidden + /> + ); }; diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/administrere_tilganger.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/administrere_tilganger.svg new file mode 100644 index 00000000000..892ca355a87 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/administrere_tilganger.svg @@ -0,0 +1,4 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3.75C9.92893 3.75 8.25 5.42893 8.25 7.5C8.25 9.57107 9.92893 11.25 12 11.25C14.0711 11.25 15.75 9.57107 15.75 7.5C15.75 5.42893 14.0711 3.75 12 3.75ZM6.75 7.5C6.75 4.60051 9.10051 2.25 12 2.25C14.8995 2.25 17.25 4.60051 17.25 7.5C17.25 10.3995 14.8995 12.75 12 12.75C9.10051 12.75 6.75 10.3995 6.75 7.5ZM12 15.75C10.6076 15.75 9.27225 16.3031 8.28769 17.2877C7.30312 18.2723 6.75 19.6076 6.75 21C6.75 21.4142 6.41421 21.75 6 21.75C5.58579 21.75 5.25 21.4142 5.25 21C5.25 19.2098 5.96116 17.4929 7.22703 16.227C8.4929 14.9612 10.2098 14.25 12 14.25C12.1884 14.25 12.3761 14.2579 12.5625 14.2735C12.9752 14.308 13.2819 14.6706 13.2474 15.0833C13.2129 15.4961 12.8503 15.8028 12.4375 15.7682C12.2926 15.7561 12.1466 15.75 12 15.75ZM14.5 21H17C17.2111 21.1056 17.7889 21.1056 18 21H19.5L19.25 20.25H14.75L14.5 21Z" fill="#23262A"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M14.7502 15.5C14.7502 14.2574 15.7576 13.25 17.0002 13.25C18.2429 13.25 19.2502 14.2574 19.2502 15.5V16.25H19.5C20.1904 16.25 20.75 16.8096 20.75 17.5V21C20.75 21.4142 20.4142 21.75 20 21.75H14C13.5858 21.75 13.25 21.4142 13.25 21V17.5C13.25 16.8096 13.8096 16.25 14.5 16.25H14.7502V15.5ZM17.7502 15.5V16.25H16.2502V15.5C16.2502 15.0858 16.586 14.75 17.0002 14.75C17.4145 14.75 17.7502 15.0858 17.7502 15.5ZM14.75 17.75V20.25H19.25V17.75H14.75Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/andre_tjenesteytende_naeringer.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/andre_tjenesteytende_naeringer.svg new file mode 100644 index 00000000000..0c1e98df199 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/andre_tjenesteytende_naeringer.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5 5.75C15.8096 5.75 15.25 6.30964 15.25 7C15.25 7.69036 15.8096 8.25 16.5 8.25C17.1904 8.25 17.75 7.69036 17.75 7C17.75 6.30964 17.1904 5.75 16.5 5.75ZM13.75 7C13.75 5.48122 14.9812 4.25 16.5 4.25C18.0188 4.25 19.25 5.48122 19.25 7C19.25 8.51878 18.0188 9.75 16.5 9.75C14.9812 9.75 13.75 8.51878 13.75 7ZM9 6.75C8.30964 6.75 7.75 7.30964 7.75 8C7.75 8.69036 8.30964 9.25 9 9.25C9.69036 9.25 10.25 8.69036 10.25 8C10.25 7.30964 9.69036 6.75 9 6.75ZM6.25 8C6.25 6.48122 7.48122 5.25 9 5.25C10.5188 5.25 11.75 6.48122 11.75 8C11.75 9.51878 10.5188 10.75 9 10.75C7.48122 10.75 6.25 9.51878 6.25 8ZM14.0223 11.4911C14.569 10.753 15.4105 10.25 16.5 10.25C17.5895 10.25 18.431 10.753 18.9777 11.4911C19.5084 12.2075 19.75 13.1231 19.75 14V15.25H21C21.4142 15.25 21.75 15.5858 21.75 16V19C21.75 19.4142 21.4142 19.75 21 19.75C20.5858 19.75 20.25 19.4142 20.25 19V16.75H12.25V19C12.25 19.4142 11.9142 19.75 11.5 19.75C11.0858 19.75 10.75 19.4142 10.75 19V15C10.75 14.3769 10.575 13.7925 10.2723 13.3839C9.98572 12.997 9.57717 12.75 9 12.75C8.42283 12.75 8.01428 12.997 7.72767 13.3839C7.42504 13.7925 7.25 14.3769 7.25 15V19C7.25 19.4142 6.91421 19.75 6.5 19.75C6.08579 19.75 5.75 19.4142 5.75 19V16.75H3.75V19C3.75 19.4142 3.41421 19.75 3 19.75C2.58579 19.75 2.25 19.4142 2.25 19V16C2.25 15.5858 2.58579 15.25 3 15.25H5.75V15C5.75 14.1231 5.99163 13.2075 6.52233 12.4911C7.06905 11.753 7.91051 11.25 9 11.25C10.0895 11.25 10.931 11.753 11.4777 12.4911C12.0084 13.2075 12.25 14.1231 12.25 15V15.25H13.25V14C13.25 13.1231 13.4916 12.2075 14.0223 11.4911ZM14.75 14C14.75 13.3769 14.925 12.7925 15.2277 12.3839C15.5143 11.997 15.9228 11.75 16.5 11.75C17.0772 11.75 17.4857 11.997 17.7723 12.3839C18.075 12.7925 18.25 13.3769 18.25 14V15.25H14.75V14Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/bygg_anlegg_og_eiendom.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/bygg_anlegg_og_eiendom.svg new file mode 100644 index 00000000000..ab486f310c8 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/bygg_anlegg_og_eiendom.svg @@ -0,0 +1,6 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M21.5 15.469C21.5 13.6724 19.5824 12.5263 18.0001 13.3772L15.3667 14.7933C15.3229 14.2488 15.1053 13.7059 14.6994 13.2796C14.2326 12.7896 13.5611 12.5 12.75 12.5H9.75C9.65501 12.5 9.49142 12.4554 9.21536 12.3224C9.11005 12.2716 9.00691 12.2176 8.89362 12.1583L8.8096 12.1143C8.67052 12.0418 8.51206 11.9606 8.35408 11.8939C7.79123 11.656 6.96829 11.5 5.75 11.5C3.99592 11.5 2.99717 12.2553 2.48188 13.1141C2.23878 13.5193 2.12021 13.9184 2.06144 14.2123C2.03178 14.3606 2.01651 14.4863 2.0086 14.5785C2.00463 14.6248 2.00249 14.663 2.00133 14.6919C2.00075 14.7064 2.00042 14.7186 2.00023 14.7283L2.00005 14.741L2.00001 14.746L2 14.7481C2 14.7481 2 14.75 2.75 14.75H2V19.25C2 19.5728 2.20657 19.8594 2.51283 19.9615L5.51283 20.9615C5.5893 20.987 5.66939 21 5.75 21H15.0484C15.6729 21 16.2787 20.7875 16.7663 20.3974L20.6086 17.3236C21.172 16.8728 21.5 16.1905 21.5 15.469ZM12.75 17.5C13.268 17.5 13.7291 17.3819 14.1162 17.1689L18.7105 14.6983C19.2935 14.3848 20 14.8071 20 15.469C20 15.7348 19.8792 15.9862 19.6716 16.1523L15.8293 19.2261C15.6077 19.4034 15.3323 19.5 15.0484 19.5H5.87171L3.5 18.7094V14.7556L3.50013 14.7519C3.50044 14.7442 3.50122 14.7287 3.50312 14.7066C3.50693 14.6622 3.51509 14.5925 3.53231 14.5065C3.56729 14.3316 3.63622 14.1057 3.76812 13.8859C4.00283 13.4947 4.50408 13 5.75 13C6.85939 13 7.46111 13.145 7.77023 13.2756C7.86661 13.3163 7.97728 13.372 8.11607 13.4444L8.18887 13.4825C8.30347 13.5425 8.43402 13.6109 8.56412 13.6736C8.86674 13.8195 9.2927 14 9.75 14H12.75C13.1889 14 13.4549 14.1479 13.6131 14.3141C13.7808 14.4902 13.875 14.7369 13.875 15C13.875 15.2631 13.7808 15.5098 13.6131 15.6859C13.4549 15.8521 13.1889 16 12.75 16H9.75C9.33579 16 9 16.3358 9 16.75C9 17.1642 9.33579 17.5 9.75 17.5H12.75Z" fill="#23262A"/> +<path d="M6.53027 7.34985C6.2374 7.64272 6.23737 8.11755 6.53021 8.41046V8.41046C6.8231 8.70341 7.29802 8.70344 7.59094 8.41052L13.1909 2.81055L18.7909 8.41052C19.0838 8.70344 19.5587 8.70341 19.8516 8.41046V8.41046C20.1445 8.11756 20.1444 7.64273 19.8516 7.34985L13.7212 1.21948C13.4282 0.926757 12.9536 0.926757 12.6606 1.21948L6.53027 7.34985Z" fill="#23262A"/> +<path d="M9.75 7C9.33579 7 9 7.33579 9 7.75V13H10.5V8.5H16V12.25C16 12.6642 16.3358 13 16.75 13V13C17.1642 13 17.5 12.6642 17.5 12.25V7.75C17.5 7.33579 17.1642 7 16.75 7H9.75Z" fill="#23262A"/> +<path d="M17 0.75C17.4142 0.75 17.75 1.0858 17.75 1.5V5.5C17.75 5.9142 17.4142 6.25 17 6.25C16.5858 6.25 16.25 5.9142 16.25 5.5V1.5C16.25 1.0858 16.5858 0.75 17 0.75Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/energi_vann_avlop_og_avfall.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/energi_vann_avlop_og_avfall.svg new file mode 100644 index 00000000000..f2999fcb670 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/energi_vann_avlop_og_avfall.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9.75006 6C9.75006 3.92893 11.429 2.25 13.5001 2.25H15.0001C17.0712 2.25 18.7501 3.92893 18.7501 6V21C18.7501 21.4142 18.4143 21.75 18.0001 21.75C17.5859 21.75 17.2501 21.4142 17.2501 21V6C17.2501 4.75736 16.2427 3.75 15.0001 3.75H13.5001C12.2575 3.75 11.2501 4.75736 11.2501 6V6.5C11.2501 6.91421 10.9143 7.25 10.5001 7.25C10.0859 7.25 9.75006 6.91421 9.75006 6.5V6ZM10.5001 7.93935L11.0304 8.46968C11.304 8.74324 11.7119 9.19716 12.0575 9.70473C12.3783 10.1758 12.7501 10.8406 12.7501 11.5C12.7501 12.7426 11.7427 13.75 10.5001 13.75C9.25742 13.75 8.25006 12.7426 8.25006 11.5C8.25006 10.8406 8.62188 10.1758 8.94263 9.70473C9.28824 9.19716 9.69617 8.74324 9.96973 8.46968L10.5001 7.93935ZM10.1825 10.549C9.87824 10.9958 9.75006 11.331 9.75006 11.5C9.75006 11.9142 10.0859 12.25 10.5001 12.25C10.9143 12.25 11.2501 11.9142 11.2501 11.5C11.2501 11.331 11.1219 10.9958 10.8177 10.549C10.7172 10.4014 10.6085 10.2574 10.5001 10.1231C10.3917 10.2574 10.283 10.4014 10.1825 10.549ZM5.93215 9.25308C6.34466 9.21558 6.70947 9.51959 6.74697 9.9321L7.27775 15.7707L7.86539 16.2114C8.56061 16.7328 9.50909 16.7591 10.2322 16.2771C11.2648 15.5887 12.5797 15.4908 13.7004 16.013L14.2532 9.9321C14.2907 9.51959 14.6555 9.21558 15.068 9.25308C15.4805 9.29058 15.7845 9.65539 15.747 10.0679L14.8297 20.1584C14.7477 21.0598 13.992 21.75 13.0869 21.75H7.91326C7.00816 21.75 6.25239 21.0598 6.17045 20.1584L5.25313 10.0679C5.21563 9.65539 5.51963 9.29058 5.93215 9.25308ZM7.66429 20.0226L7.45494 17.7198C8.58952 18.3114 9.97463 18.2516 11.0643 17.5252C11.6763 17.1172 12.46 17.0684 13.1179 17.3973L13.5547 17.6157L13.3358 20.0226C13.3241 20.1514 13.2162 20.25 13.0869 20.25H7.91326C7.78396 20.25 7.67599 20.1514 7.66429 20.0226Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/forhold_ved_virksomheten.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/forhold_ved_virksomheten.svg new file mode 100644 index 00000000000..b04ec5395d4 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/forhold_ved_virksomheten.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M6.25 3C6.25 2.58579 6.58579 2.25 7 2.25H17C17.4142 2.25 17.75 2.58579 17.75 3V9.25H21C21.4142 9.25 21.75 9.58579 21.75 10V21C21.75 21.4142 21.4142 21.75 21 21.75H3C2.58579 21.75 2.25 21.4142 2.25 21V10C2.25 9.58579 2.58579 9.25 3 9.25H6.25V3ZM17.75 20.25V10.75H20.25V20.25H17.75ZM16.25 3.75V20.25H7.75V3.75H16.25ZM6.25 10.75V20.25H3.75V10.75H6.25ZM10 5.75C10.4142 5.75 10.75 6.08579 10.75 6.5V8.5C10.75 8.91421 10.4142 9.25 10 9.25C9.58579 9.25 9.25 8.91421 9.25 8.5V6.5C9.25 6.08579 9.58579 5.75 10 5.75ZM13 5.75C13.4142 5.75 13.75 6.08579 13.75 6.5V8.5C13.75 8.91421 13.4142 9.25 13 9.25C12.5858 9.25 12.25 8.91421 12.25 8.5V6.5C12.25 6.08579 12.5858 5.75 13 5.75ZM10.75 12.5C10.75 12.0858 10.4142 11.75 10 11.75C9.58579 11.75 9.25 12.0858 9.25 12.5V14.5C9.25 14.9142 9.58579 15.25 10 15.25C10.4142 15.25 10.75 14.9142 10.75 14.5V12.5ZM13 11.75C13.4142 11.75 13.75 12.0858 13.75 12.5V14.5C13.75 14.9142 13.4142 15.25 13 15.25C12.5858 15.25 12.25 14.9142 12.25 14.5V12.5C12.25 12.0858 12.5858 11.75 13 11.75Z" fill="#111D46"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_konkursbo.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_konkursbo.svg new file mode 100644 index 00000000000..828702e24a3 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_konkursbo.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M14 11.0607L18.4215 15.4822H16.7676C16.3534 15.4822 16.0176 15.818 16.0176 16.2322C16.0176 16.6464 16.3534 16.9822 16.7676 16.9822H20.3031C20.7173 16.9822 21.0531 16.6464 21.0531 16.2322V12.6967C21.0531 12.2824 20.7173 11.9467 20.3031 11.9467C19.8889 11.9467 19.5531 12.2824 19.5531 12.6967V14.4925L14.5303 9.46967C14.2374 9.17678 13.7626 9.17678 13.4697 9.46967L10 12.9393L4.53033 7.46967C4.23744 7.17678 3.76256 7.17678 3.46967 7.46967C3.17678 7.76256 3.17678 8.23744 3.46967 8.53033L9.46967 14.5303C9.76256 14.8232 10.2374 14.8232 10.5303 14.5303L14 11.0607Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_regnskapsforer.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_regnskapsforer.svg new file mode 100644 index 00000000000..fd4744d99ab --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_regnskapsforer.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M4.25 4C4.25 3.0335 5.0335 2.25 6 2.25H18C18.9665 2.25 19.75 3.0335 19.75 4V20C19.75 20.9665 18.9665 21.75 18 21.75H6C5.0335 21.75 4.25 20.9665 4.25 20V4ZM6 3.75C5.86193 3.75 5.75 3.86193 5.75 4V20C5.75 20.1381 5.86193 20.25 6 20.25H18C18.1381 20.25 18.25 20.1381 18.25 20V4C18.25 3.86193 18.1381 3.75 18 3.75H6ZM8 11C8 10.4477 8.44772 10 9 10C9.55228 10 10 10.4477 10 11C10 11.5523 9.55228 12 9 12C8.44772 12 8 11.5523 8 11ZM9 13C8.44772 13 8 13.4477 8 14C8 14.5523 8.44772 15 9 15C9.55228 15 10 14.5523 10 14C10 13.4477 9.55228 13 9 13ZM8 17C8 16.4477 8.44772 16 9 16C9.55228 16 10 16.4477 10 17C10 17.5523 9.55228 18 9 18C8.44772 18 8 17.5523 8 17ZM12 10C11.4477 10 11 10.4477 11 11C11 11.5523 11.4477 12 12 12C12.5523 12 13 11.5523 13 11C13 10.4477 12.5523 10 12 10ZM11 14C11 13.4477 11.4477 13 12 13C12.5523 13 13 13.4477 13 14C13 14.5523 12.5523 15 12 15C11.4477 15 11 14.5523 11 14ZM12 16C11.4477 16 11 16.4477 11 17C11 17.5523 11.4477 18 12 18C12.5523 18 13 17.5523 13 17C13 16.4477 12.5523 16 12 16ZM14 11C14 10.4477 14.4477 10 15 10C15.5523 10 16 10.4477 16 11C16 11.5523 15.5523 12 15 12C14.4477 12 14 11.5523 14 11ZM15 13C14.4477 13 14 13.4477 14 14C14 14.5523 14.4477 15 15 15C15.5523 15 16 14.5523 16 14C16 13.4477 15.5523 13 15 13ZM14 17C14 16.4477 14.4477 16 15 16C15.5523 16 16 16.4477 16 17C16 17.5523 15.5523 18 15 18C14.4477 18 14 17.5523 14 17ZM8.5 6.25C8.08579 6.25 7.75 6.58579 7.75 7C7.75 7.41421 8.08579 7.75 8.5 7.75H15.5C15.9142 7.75 16.25 7.41421 16.25 7C16.25 6.58579 15.9142 6.25 15.5 6.25H8.5Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_revisor.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_revisor.svg new file mode 100644 index 00000000000..d82a10b63a2 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/fullmakter_for_revisor.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 -2 16 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0.25C0.58579 0.25 0.25 0.58579 0.25 1V19C0.25 19.4142 0.58579 19.75 1 19.75H15C15.4142 19.75 15.75 19.4142 15.75 19V1C15.75 0.58579 15.4142 0.25 15 0.25H1ZM10 18.25H11.5601H14.25V1.75H1.75V18.25H7.5H8.2101H10ZM4.5 3.25C4.91421 3.25 5.25 3.5858 5.25 4V5C5.25 5.4142 4.91421 5.75 4.5 5.75C4.08579 5.75 3.75 5.4142 3.75 5V4C3.75 3.5858 4.08579 3.25 4.5 3.25ZM8.75 4C8.75 3.5858 8.4142 3.25 8 3.25C7.5858 3.25 7.25 3.5858 7.25 4V5C7.25 5.4142 7.5858 5.75 8 5.75C8.4142 5.75 8.75 5.4142 8.75 5V4ZM11.5 3.25C11.9142 3.25 12.25 3.5858 12.25 4V5C12.25 5.4142 11.9142 5.75 11.5 5.75C11.0858 5.75 10.75 5.4142 10.75 5V4C10.75 3.5858 11.0858 3.25 11.5 3.25ZM10.75 11C10.75 9.2051 9.2949 7.75 7.5 7.75C5.7051 7.75 4.25 9.2051 4.25 11C4.25 12.7949 5.7051 14.25 7.5 14.25C8.1257 14.25 8.7102 14.0732 9.2061 13.7667L10.9697 15.5303C11.2626 15.8232 11.7374 15.8232 12.0303 15.5303C12.3232 15.2374 12.3232 14.7626 12.0303 14.4697L10.2667 12.7061C10.5732 12.2102 10.75 11.6257 10.75 11ZM7.5 9.25C8.4665 9.25 9.25 10.0335 9.25 11C9.25 11.9665 8.4665 12.75 7.5 12.75C6.5335 12.75 5.75 11.9665 5.75 11C5.75 10.0335 6.5335 9.25 7.5 9.25Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/handel_overnatting_og_servering.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/handel_overnatting_og_servering.svg new file mode 100644 index 00000000000..8e5f178048e --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/handel_overnatting_og_servering.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9.44391 5.49675C9.58609 5.33963 9.7881 5.25 10 5.25H15C15.2119 5.25 15.4139 5.33963 15.5561 5.49675C15.6983 5.65386 15.7674 5.86378 15.7463 6.07463L15.3908 9.62932C15.3496 10.0415 14.9821 10.3422 14.5699 10.301C14.1577 10.2597 13.857 9.89222 13.8983 9.48006L14.1713 6.75H10.8287L11.3221 11.6834C11.3633 12.0956 11.0626 12.4631 10.6504 12.5043C10.2383 12.5455 9.87074 12.2448 9.82953 11.8327L9.25372 6.07463C9.23264 5.86378 9.30173 5.65386 9.44391 5.49675ZM4.75 7C4.75 6.58579 4.41421 6.25 4 6.25C3.58579 6.25 3.25 6.58579 3.25 7V7.38197C3.25 8.04482 3.6245 8.65078 4.21738 8.94721L5.1118 9.39443C5.1965 9.43678 5.25 9.52334 5.25 9.61803V10C5.25 10.4142 5.58579 10.75 6 10.75C6.41421 10.75 6.75 10.4142 6.75 10V9.61803C6.75 8.95518 6.3755 8.34922 5.78262 8.05279L4.8882 7.60557C4.8035 7.56322 4.75 7.47666 4.75 7.38197V7ZM2.93774 11.5036C3.08011 11.3424 3.28488 11.25 3.5 11.25H8.5C8.71512 11.25 8.91989 11.3424 9.06226 11.5036C9.20463 11.6649 9.27089 11.8796 9.24421 12.093L8.9632 14.3411C8.79118 15.7173 7.62132 16.75 6.23444 16.75H5.76556C4.37868 16.75 3.20882 15.7173 3.0368 14.3411L2.75579 12.093C2.72911 11.8796 2.79537 11.6649 2.93774 11.5036ZM4.34959 12.75L4.52522 14.155C4.60341 14.7806 5.13516 15.25 5.76556 15.25H6.23444C6.86484 15.25 7.39659 14.7806 7.47478 14.155L7.65041 12.75H4.34959ZM2.25 18C2.25 17.5858 2.58579 17.25 3 17.25H21C21.4142 17.25 21.75 17.5858 21.75 18C21.75 18.4142 21.4142 18.75 21 18.75H3C2.58579 18.75 2.25 18.4142 2.25 18ZM15.5 11.25C13.1229 11.25 11.1537 12.9961 10.8049 15.2756C10.4853 15.3615 10.25 15.6533 10.25 16C10.25 16.4142 10.5858 16.75 11 16.75H20C20.4142 16.75 20.75 16.4142 20.75 16C20.75 15.6533 20.5147 15.3615 20.1951 15.2756C19.8463 12.9961 17.8771 11.25 15.5 11.25ZM15.5 12.75C13.9632 12.75 12.6755 13.8167 12.337 15.25H18.663C18.3245 13.8167 17.0368 12.75 15.5 12.75Z" fill="#111D46"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/helse_pleie_omsorg_og_vern.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/helse_pleie_omsorg_og_vern.svg new file mode 100644 index 00000000000..f82b3bb04dd --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/helse_pleie_omsorg_og_vern.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M4.25 3C4.25 2.58579 4.58579 2.25 5 2.25H19C19.4142 2.25 19.75 2.58579 19.75 3V21C19.75 21.4142 19.4142 21.75 19 21.75H5C4.58579 21.75 4.25 21.4142 4.25 21V3ZM16.25 17C16.25 16.5858 15.9142 16.25 15.5 16.25H12C11.5858 16.25 11.25 16.5858 11.25 17V20.25H5.75V3.75H18.25V20.25H16.25V17ZM12.75 17.75V20.25H14.75V17.75H12.75ZM12.75 6C12.75 5.58579 12.4142 5.25 12 5.25C11.5858 5.25 11.25 5.58579 11.25 6V7.25H10C9.58579 7.25 9.25 7.58579 9.25 8C9.25 8.41421 9.58579 8.75 10 8.75H11.25V10C11.25 10.4142 11.5858 10.75 12 10.75C12.4142 10.75 12.75 10.4142 12.75 10V8.75H14C14.4142 8.75 14.75 8.41421 14.75 8C14.75 7.58579 14.4142 7.25 14 7.25H12.75V6ZM9.25 13C9.25 12.5858 8.91421 12.25 8.5 12.25C8.08579 12.25 7.75 12.5858 7.75 13V14C7.75 14.4142 8.08579 14.75 8.5 14.75C8.91421 14.75 9.25 14.4142 9.25 14V13ZM9.25 17C9.25 16.5858 8.91421 16.25 8.5 16.25C8.08579 16.25 7.75 16.5858 7.75 17V18C7.75 18.4142 8.08579 18.75 8.5 18.75C8.91421 18.75 9.25 18.4142 9.25 18V17ZM12 12.25C12.4142 12.25 12.75 12.5858 12.75 13V14C12.75 14.4142 12.4142 14.75 12 14.75C11.5858 14.75 11.25 14.4142 11.25 14V13C11.25 12.5858 11.5858 12.25 12 12.25ZM16.25 13C16.25 12.5858 15.9142 12.25 15.5 12.25C15.0858 12.25 14.75 12.5858 14.75 13V14C14.75 14.4142 15.0858 14.75 15.5 14.75C15.9142 14.75 16.25 14.4142 16.25 14V13Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/index.ts b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/index.ts new file mode 100644 index 00000000000..93fe1d61336 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/index.ts @@ -0,0 +1,20 @@ +export { default as skatt_avgift_regnskap_og_toll } from './skatt_avgift_regnskap_og_toll.svg'; +export { default as personale } from './personale.svg'; +export { default as miljo_ulykke_og_sikkerhet } from './miljo_ulykke_og_sikkerhet.svg'; +export { default as post_og_arkiv } from './post_og_arkiv.svg'; +export { default as forhold_ved_virksomheten } from './forhold_ved_virksomheten.svg'; +export { default as integrasjoner } from './integrasjoner.svg'; +export { default as administrere_tilganger } from './administrere_tilganger.svg'; +export { default as jordbruk_skogbruk_jakt_fiske_og_akvakultur } from './jordbruk_skogbruk_jakt_fiske_og_akvakultur.svg'; +export { default as bygg_anlegg_og_eiendom } from './bygg_anlegg_og_eiendom.svg'; +export { default as transport_og_lagring } from './transport_og_lagring.svg'; +export { default as helse_pleie_omsorg_og_vern } from './helse_pleie_omsorg_og_vern.svg'; +export { default as oppvekst_og_utdaning } from './oppvekst_og_utdaning.svg'; +export { default as energi_vann_avlop_og_avfall } from './energi_vann_avlop_og_avfall.svg'; +export { default as industrier } from './industrier.svg'; +export { default as kultur_og_frivillighet } from './kultur_og_frivillighet.svg'; +export { default as handel_overnatting_og_servering } from './handel_overnatting_og_servering.svg'; +export { default as andre_tjenesteytende_naeringer } from './andre_tjenesteytende_naeringer.svg'; +export { default as fullmakter_for_revisor } from './fullmakter_for_revisor.svg'; +export { default as fullmakter_for_regnskapsforer } from './fullmakter_for_regnskapsforer.svg'; +export { default as fullmakter_for_konkursbo } from './fullmakter_for_konkursbo.svg'; diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/industrier.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/industrier.svg new file mode 100644 index 00000000000..9ee4ee93047 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/industrier.svg @@ -0,0 +1,9 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M20.2674 22C20.4612 22 20.6438 21.8876 20.7605 21.6964C20.8772 21.5053 20.9146 21.2575 20.8613 21.0273L20.2719 3.55349C20.1962 3.22587 19.9538 3 19.678 3L17.2075 3C16.9317 3 16.6893 3.22587 16.6136 3.55349L15.9089 21.0273C15.8556 21.2575 14.8929 21.5053 15.0096 21.6964C15.1264 21.8876 15.3089 22 15.5027 22L20.2674 22ZM18.9428 4.52617L19.5347 20.2966H17.5L17.9428 4.52617L18.9428 4.52617Z" fill="#23262A"/> +<path d="M3 21.25C3 21.6642 3.33579 22 3.75 22L17 22L17 20.5L4.5 20.5L4.5 12L16.25 12C16.6642 12 17 11.6642 17 11.25C17 10.8358 16.6642 10.5 16.25 10.5L3.75 10.5C3.33579 10.5 3 10.8358 3 11.25L3 21.25Z" fill="#23262A"/> +<path d="M6.75 14.5C7.16421 14.5 7.5 14.8358 7.5 15.25V17.25C7.5 17.6642 7.16421 18 6.75 18C6.33579 18 6 17.6642 6 17.25V15.25C6 14.8358 6.33579 14.5 6.75 14.5Z" fill="#23262A"/> +<path d="M11 15.25C11 14.8358 10.6642 14.5 10.25 14.5C9.8358 14.5 9.5 14.8358 9.5 15.25V17.25C9.5 17.6642 9.8358 18 10.25 18C10.6642 18 11 17.6642 11 17.25V15.25Z" fill="#23262A"/> +<path d="M13.75 14.5C14.1642 14.5 14.5 14.8358 14.5 15.25V17.25C14.5 17.6642 14.1642 18 13.75 18C13.3358 18 13 17.6642 13 17.25V15.25C13 14.8358 13.3358 14.5 13.75 14.5Z" fill="#23262A"/> +<path d="M3.75 10.25V6.5L8.75 10.25H3.75Z" fill="#23262A" stroke="#23262A" stroke-width="1.5"/> +<path d="M9.75 10.25V6.5L14.75 10.25H9.75Z" fill="#23262A" stroke="#23262A" stroke-width="1.5"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/integrasjoner.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/integrasjoner.svg new file mode 100644 index 00000000000..3047a61bc52 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/integrasjoner.svg @@ -0,0 +1,10 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0303 1.21967C17.3232 1.51256 17.3232 1.98744 17.0303 2.28033L15.8107 3.5H18C20.0711 3.5 21.75 5.17893 21.75 7.25V9.75C21.75 10.1642 21.4142 10.5 21 10.5C20.5858 10.5 20.25 10.1642 20.25 9.75V7.25C20.25 6.00736 19.2426 5 18 5H15.8107L17.0303 6.21967C17.3232 6.51256 17.3232 6.98744 17.0303 7.28033C16.7374 7.57322 16.2626 7.57322 15.9697 7.28033L13.4697 4.78033C13.3978 4.70842 13.3435 4.62555 13.3069 4.53709C13.2702 4.44866 13.25 4.35169 13.25 4.25C13.25 4.05806 13.3232 3.86612 13.4697 3.71967L15.9697 1.21967C16.2626 0.926777 16.7374 0.926777 17.0303 1.21967Z" fill="#23262A"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M6.7197 22.7803C6.4268 22.4874 6.4268 22.0126 6.7197 21.7197L7.9393 20.5L5.75 20.5C3.6789 20.5 2 18.8211 2 16.75L2 14.25C2 13.8358 2.3358 13.5 2.75 13.5C3.1642 13.5 3.5 13.8358 3.5 14.25L3.5 16.75C3.5 17.9926 4.5074 19 5.75 19L7.9393 19L6.7197 17.7803C6.4268 17.4874 6.4268 17.0126 6.7197 16.7197C7.0126 16.4268 7.4874 16.4268 7.7803 16.7197L10.2803 19.2197C10.3522 19.2916 10.4065 19.3745 10.4431 19.4629C10.4798 19.5513 10.5 19.6483 10.5 19.75C10.5 19.9419 10.4268 20.1339 10.2803 20.2803L7.7803 22.7803C7.4874 23.0732 7.0126 23.0732 6.7197 22.7803Z" fill="#23262A"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M20 19.5C20.9665 19.5 21.75 18.7165 21.75 17.75L21.75 13.75C21.75 12.7835 20.9665 12 20 12L14 12C13.0335 12 12.25 12.7835 12.25 13.75L12.25 17.75C12.25 18.7165 13.0335 19.5 14 19.5L20 19.5ZM20.25 17.75C20.25 17.8881 20.1381 18 20 18L14 18C13.8619 18 13.75 17.9043 13.75 17.7662L13.75 13.75C13.75 13.6119 13.8619 13.5 14 13.5L20 13.5C20.1381 13.5 20.25 13.6119 20.25 13.75L20.25 17.75Z" fill="#23262A"/> +<path d="M20.5007 23L16.75 23L15.25 23L13.4999 23C13.0857 22.9999 12.75 22.6642 12.75 22.2499C12.75 21.8357 13.0857 21.5 13.4999 21.5L15.25 21.5L16.75 21.5L20.5007 21.5C20.9149 21.5 21.25 21.8358 21.25 22.25C21.25 22.6642 20.9149 23 20.5007 23Z" fill="#23262A"/> +<path d="M17.75 18.75L17.75 19.7308L17.75 20.7692L17.75 21.75C17.75 22.1642 17.4142 22.5 16.9999 22.5C16.5857 22.5 16.25 22.1643 16.25 21.7501L16.25 20.7692L16.25 19.7308L16.25 18.7499C16.25 18.3357 16.5858 18 16.9999 18C17.4142 18 17.75 18.3358 17.75 18.75Z" fill="#23262A"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9.75 8.5C10.7165 8.5 11.5 7.7165 11.5 6.75L11.5 2.75C11.5 1.7835 10.7165 0.999999 9.75 0.999999L3.75 0.999998C2.7835 0.999998 2 1.7835 2 2.75L2 6.75C2 7.7165 2.7835 8.5 3.75 8.5L9.75 8.5ZM10 6.75C10 6.8881 9.88807 7 9.75 7L3.75 7C3.6119 7 3.5 6.90427 3.5 6.76617L3.5 2.75C3.5 2.6119 3.6119 2.5 3.75 2.5L9.75 2.5C9.88807 2.5 10 2.6119 10 2.75L10 6.75Z" fill="#23262A"/> +<path d="M10.2507 12L6.5 12L5 12L3.24994 12C2.83573 11.9999 2.5 11.6642 2.5 11.2499C2.5 10.8357 2.83571 10.5 3.24992 10.5L5 10.5L6.5 10.5L10.2507 10.5C10.6649 10.5 11 10.8358 11 11.25C11 11.6642 10.6649 12 10.2507 12Z" fill="#23262A"/> +<path d="M7.49997 7.74998L7.5 8.73077L7.5 9.76923L7.49997 10.75C7.49995 11.1642 7.16416 11.5 6.74994 11.5C6.33573 11.5 5.99996 11.1643 5.99998 10.7501L6 9.76923L6 8.73077L6 7.74991C6 7.33572 6.33576 7 6.74995 7C7.16416 7 7.49996 7.33577 7.49997 7.74998Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/jordbruk_skogbruk_jakt_fiske_og_akvakultur.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/jordbruk_skogbruk_jakt_fiske_og_akvakultur.svg new file mode 100644 index 00000000000..16ecd52d4d3 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/jordbruk_skogbruk_jakt_fiske_og_akvakultur.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M13.9083 8.18705C14.3722 7.97584 14.8665 7.76867 15.3397 7.60407C15.8938 7.41135 16.493 7.24994 17.0002 7.24994C17.8975 7.24994 18.8335 7.54506 19.494 8.12301C20.1905 8.73241 20.5696 9.66339 20.2117 10.7371C19.8764 11.7429 19.1412 12.354 18.2749 12.6656C17.4353 12.9676 16.4665 12.9952 15.5508 12.8857C14.6264 12.7752 13.6984 12.518 12.9066 12.1965C12.3983 11.9901 11.9156 11.7447 11.5176 11.4744C11.3418 11.6614 11.1214 11.9356 10.8966 12.3178C10.3982 13.165 9.861 14.5725 9.76516 16.79C12.2088 17.0519 13.5421 18.5434 14.1824 19.6154C14.3948 19.971 14.2787 20.4315 13.9231 20.6439C13.5675 20.8563 13.1071 20.7402 12.8946 20.3846C12.3631 19.4947 11.2411 18.25 9.00003 18.25C6.75895 18.25 5.637 19.4947 5.10541 20.3846C4.893 20.7402 4.43253 20.8563 4.07693 20.6439C3.72133 20.4315 3.60525 19.971 3.81767 19.6154C4.46052 18.5392 5.80188 17.04 8.26401 16.787C8.36196 14.3155 8.96305 12.6463 9.60366 11.5572C9.896 11.0603 10.1931 10.6902 10.4466 10.4239C9.70648 9.45188 8.96447 7.99404 8.70064 6.54464C8.53541 5.63694 8.54382 4.65732 8.93223 3.78192C9.3331 2.87846 10.0991 2.17639 11.263 1.78843C11.8208 1.6025 12.3617 1.64605 12.8379 1.86584C13.2968 2.07763 13.6568 2.43375 13.9271 2.81984C14.461 3.58259 14.7502 4.61096 14.7502 5.49994C14.7502 6.01433 14.5852 6.5865 14.3885 7.10288C14.2502 7.46594 14.0827 7.83533 13.9083 8.18705ZM12.2093 3.22778C12.0762 3.16633 11.9295 3.14739 11.7373 3.21145C10.9292 3.48085 10.515 3.91325 10.3033 4.39027C10.0792 4.89536 10.0428 5.54225 10.1764 6.276C10.3828 7.41001 10.9616 8.57122 11.5228 9.35677C11.5658 9.28892 11.6168 9.2075 11.6739 9.11468C11.8559 8.81896 12.0977 8.41091 12.3384 7.9595C12.5803 7.506 12.8146 7.0208 12.9868 6.56888C13.1651 6.10088 13.2502 5.73555 13.2502 5.49994C13.2502 4.88892 13.0393 4.16729 12.6982 3.68004C12.531 3.44113 12.3598 3.29724 12.2093 3.22778ZM12.7257 10.4556C12.7854 10.4232 12.8515 10.3876 12.9234 10.3492C13.2675 10.1657 13.7403 9.92173 14.257 9.67856C14.7757 9.43445 15.3277 9.19638 15.8325 9.02081C16.3566 8.83853 16.7573 8.74994 17.0002 8.74994C17.6028 8.74994 18.1668 8.95482 18.5063 9.25187C18.8098 9.51747 18.9307 9.8365 18.7887 10.2628C18.6234 10.7584 18.2801 11.0697 17.7672 11.2542C17.2275 11.4483 16.5164 11.4905 15.7289 11.3963C14.95 11.3032 14.1516 11.0831 13.471 10.8067C13.1893 10.6923 12.9394 10.573 12.7257 10.4556Z" fill="#111D46"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/kultur_og_frivillighet.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/kultur_og_frivillighet.svg new file mode 100644 index 00000000000..1d0ecce221c --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/kultur_og_frivillighet.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75001 11C5.75001 6.99594 8.99594 3.75 13 3.75C17.0041 3.75 20.25 6.99594 20.25 11C20.25 12.8392 19.5661 14.5168 18.4377 15.7953L18.25 16.008V16.2916V21.5C18.25 21.9142 18.5858 22.25 19 22.25C19.4142 22.25 19.75 21.9142 19.75 21.5V16.5681C20.999 15.0556 21.75 13.1147 21.75 11C21.75 6.16751 17.8325 2.25 13 2.25C8.20505 2.25 4.31092 6.10689 4.25071 10.8876L2.78164 15.7845C2.71351 16.0116 2.75677 16.2575 2.89829 16.4477C3.03981 16.6379 3.26293 16.75 3.50001 16.75H5.25001V18C5.25001 19.5188 6.48122 20.75 8.00001 20.75H10.25V21.5C10.25 21.9142 10.5858 22.25 11 22.25C11.4142 22.25 11.75 21.9142 11.75 21.5V20C11.75 19.5858 11.4142 19.25 11 19.25H8.00001C7.30965 19.25 6.75001 18.6904 6.75001 18V16C6.75001 15.5858 6.41422 15.25 6.00001 15.25H4.50803L5.71838 11.2155L5.75001 11.1101V11ZM10.7803 8.77811C11.1777 8.38065 11.8222 8.38065 12.2196 8.77811L12.4696 9.02811C12.7625 9.321 13.2374 9.321 13.5303 9.02811L13.7803 8.77811C14.1777 8.38065 14.8222 8.38065 15.2196 8.77811C15.6171 9.17557 15.6171 9.81998 15.2196 10.2174L13 12.4371L10.7803 10.2174C10.3828 9.81998 10.3828 9.17557 10.7803 8.77811ZM9.71962 7.71745C10.6141 6.82296 12.0142 6.74221 13 7.4752C13.9857 6.74221 15.3858 6.82296 16.2803 7.71745C17.2635 8.7007 17.2635 10.2949 16.2803 11.2781L13.5303 14.0281C13.2374 14.321 12.7625 14.321 12.4696 14.0281L9.71962 11.2781C8.73637 10.2949 8.73637 8.7007 9.71962 7.71745Z" fill="#262626"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/miljo_ulykke_og_sikkerhet.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/miljo_ulykke_og_sikkerhet.svg new file mode 100644 index 00000000000..ee0f517a63f --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/miljo_ulykke_og_sikkerhet.svg @@ -0,0 +1,4 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M21.5 15.469C21.5 13.6724 19.5824 12.5263 18.0001 13.3772L15.3667 14.7933C15.3229 14.2488 15.1053 13.7059 14.6994 13.2796C14.2326 12.7896 13.5611 12.5 12.75 12.5H9.75C9.65501 12.5 9.49142 12.4554 9.21536 12.3224C9.11005 12.2716 9.00691 12.2176 8.89362 12.1583L8.8096 12.1143C8.67052 12.0418 8.51206 11.9606 8.35408 11.8939C7.79123 11.656 6.96829 11.5 5.75 11.5C3.99592 11.5 2.99717 12.2553 2.48188 13.1141C2.23878 13.5193 2.12021 13.9184 2.06144 14.2123C2.03178 14.3606 2.01651 14.4863 2.0086 14.5785C2.00463 14.6248 2.00249 14.663 2.00133 14.6919C2.00075 14.7064 2.00042 14.7186 2.00023 14.7283L2.00005 14.741L2.00001 14.746L2 14.7481C2 14.7481 2 14.75 2.75 14.75H2V19.25C2 19.5728 2.20657 19.8594 2.51283 19.9615L5.51283 20.9615C5.5893 20.987 5.66939 21 5.75 21H15.0484C15.6729 21 16.2787 20.7875 16.7663 20.3974L20.6086 17.3236C21.172 16.8728 21.5 16.1905 21.5 15.469ZM12.75 17.5C13.268 17.5 13.7291 17.3819 14.1162 17.1689L18.7105 14.6983C19.2935 14.3848 20 14.8071 20 15.469C20 15.7348 19.8792 15.9862 19.6716 16.1523L15.8293 19.2261C15.6077 19.4034 15.3323 19.5 15.0484 19.5H5.87171L3.5 18.7094V14.7556L3.50013 14.7519C3.50044 14.7442 3.50122 14.7287 3.50312 14.7066C3.50693 14.6622 3.51509 14.5925 3.53231 14.5065C3.56729 14.3316 3.63622 14.1057 3.76812 13.8859C4.00283 13.4947 4.50408 13 5.75 13C6.85939 13 7.46111 13.145 7.77023 13.2756C7.86661 13.3163 7.97728 13.372 8.11607 13.4444L8.18887 13.4825C8.30347 13.5425 8.43402 13.6109 8.56412 13.6736C8.86674 13.8195 9.2927 14 9.75 14H12.75C13.1889 14 13.4549 14.1479 13.6131 14.3141C13.7808 14.4902 13.875 14.7369 13.875 15C13.875 15.2631 13.7808 15.5098 13.6131 15.6859C13.4549 15.8521 13.1889 16 12.75 16H9.75C9.33579 16 9 16.3358 9 16.75C9 17.1642 9.33579 17.5 9.75 17.5H12.75Z" fill="#23262A"/> +<path d="M9.82465 3.96262C9.87666 4.09249 9.93463 4.22328 9.99594 4.35232C9.78212 4.26543 9.56371 4.18308 9.35107 4.11272C8.98216 3.99065 8.5702 3.88365 8.21397 3.88365C7.59772 3.88365 6.94869 4.07586 6.48514 4.46172C5.99174 4.87243 5.71589 5.51049 5.97267 6.24335C6.20972 6.91988 6.73021 7.3278 7.33072 7.53328C7.90985 7.73145 8.57085 7.74767 9.18639 7.67764C9.80909 7.6068 10.4331 7.44225 10.9655 7.23653C11.271 7.1185 11.565 6.97975 11.8184 6.82598C11.9153 6.93274 12.028 7.0758 12.1431 7.26195C12.4515 7.76068 12.7902 8.59062 12.8666 9.89837C12.0013 10.0085 11.345 10.3375 10.8577 10.7255C10.6113 10.905 10.5058 11.1851 10.5508 11.4224C10.5736 11.5425 10.6361 11.6541 10.7415 11.7285C10.8475 11.8033 10.9845 11.8318 11.1398 11.8087C11.3653 11.7752 11.5986 11.6914 11.8121 11.5249C12.1722 11.2439 12.4028 11.1317 12.6298 11.0842C12.8129 11.0458 12.996 11.0479 13.2552 11.0507C13.3308 11.0516 13.4129 11.0525 13.5034 11.0525C14.9305 11.0525 15.6369 11.8022 15.9725 12.3366C16.1499 12.6191 16.5291 12.7072 16.8201 12.5419C17.1145 12.3746 17.2155 12.0063 17.0356 11.7198C16.6063 11.036 15.7191 10.0889 14.1099 9.89461C14.0308 8.379 13.6363 7.34067 13.2106 6.65212C13.0407 6.37731 12.8679 6.16174 12.7125 5.99626C13.1888 5.37427 13.6532 4.48148 13.8241 3.58852C13.9363 3.00233 13.9332 2.35708 13.6615 1.77449C13.3795 1.1699 12.8426 0.707247 12.045 0.454313C11.6471 0.32813 11.2582 0.357324 10.9157 0.507703C10.587 0.652 10.3329 0.892841 10.1451 1.14812C9.77509 1.65098 9.57659 2.32423 9.57659 2.90793C9.57659 3.25584 9.69294 3.63371 9.82465 3.96262ZM11.4318 1.59324C11.4921 1.56673 11.5578 1.5566 11.6558 1.58767C12.1619 1.74816 12.4053 1.99899 12.5284 2.26281C12.6617 2.54857 12.6882 2.9264 12.6028 3.37257C12.4835 3.99627 12.1676 4.63878 11.8402 5.11322C11.7209 4.92875 11.5626 4.6746 11.4052 4.39382C11.2467 4.11115 11.0946 3.81115 10.9835 3.53381C10.8672 3.24339 10.8183 3.03167 10.8183 2.90793C10.8183 2.5482 10.9504 2.12073 11.1589 1.83733C11.2603 1.69958 11.3574 1.62588 11.4318 1.59324ZM10.759 6.0222C10.679 6.05818 10.5933 6.09403 10.5023 6.1292C10.0613 6.29957 9.54378 6.43517 9.04037 6.49245C8.52982 6.55053 8.08011 6.52283 7.74733 6.40896C7.43633 6.30254 7.24342 6.13048 7.14946 5.86233C7.10968 5.74879 7.10986 5.65981 7.13345 5.58581C7.15747 5.51044 7.21007 5.43722 7.29815 5.3639C7.49586 5.19932 7.83862 5.07712 8.21397 5.07712C8.35352 5.07712 8.60262 5.1273 8.94673 5.24117C9.27491 5.34976 9.63565 5.49767 9.97657 5.65029C10.2711 5.78215 10.5441 5.91447 10.759 6.0222Z" fill="#262626" stroke="#262626" stroke-width="0.25"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/oppvekst_og_utdaning.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/oppvekst_og_utdaning.svg new file mode 100644 index 00000000000..c155941d134 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/oppvekst_og_utdaning.svg @@ -0,0 +1,7 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M6.25 3C6.25 2.58579 6.58579 2.25 7 2.25L21 2.25C21.4142 2.25 21.75 2.58579 21.75 3V9.25V9.42305C21.75 9.65002 21.75 9.58579 21.75 10V21C21.75 21.4142 21.4142 21.75 21 21.75H3C2.58579 21.75 2.25 21.4142 2.25 21V10C2.25 9.58579 2.58579 9.25 3 9.25H6.25V3ZM20.25 3.75V20.25H7.75V3.75H20.25ZM6.25 10.75V20.25H3.75V10.75H6.25Z" fill="#23262A"/> +<path d="M18 15.5C18 15.0858 17.6642 14.75 17.25 14.75C16.8358 14.75 16.5 15.0858 16.5 15.5V17.5C16.5 17.9142 16.8358 18.25 17.25 18.25C17.6642 18.25 18 17.9142 18 17.5V15.5Z" fill="#23262A"/> +<path d="M14.75 15.5C14.75 15.0858 14.4142 14.75 14 14.75C13.5858 14.75 13.25 15.0858 13.25 15.5V17.5C13.25 17.9142 13.5858 18.25 14 18.25C14.4142 18.25 14.75 17.9142 14.75 17.5V15.5Z" fill="#23262A"/> +<path d="M11.5 15.5C11.5 15.0858 11.1642 14.75 10.75 14.75C10.3358 14.75 10 15.0858 10 15.5V17.5C10 17.9142 10.3358 18.25 10.75 18.25C11.1642 18.25 11.5 17.9142 11.5 17.5V15.5Z" fill="#23262A"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2632 9C12.2632 7.48848 13.4885 6.26316 15 6.26316C16.5115 6.26316 17.7368 7.48848 17.7368 9C17.7368 10.5115 16.5115 11.7368 15 11.7368C13.4885 11.7368 12.2632 10.5115 12.2632 9ZM15 5C12.7909 5 11 6.79086 11 9C11 11.2092 12.7909 13 15 13C17.2092 13 19 11.2092 19 9C19 6.79086 17.2092 5 15 5ZM15 6.68421C15.3488 6.68421 15.6316 6.96698 15.6316 7.31579V8.73839L16.2887 9.39551C16.5353 9.64216 16.5353 10.0421 16.2887 10.2887C16.0421 10.5353 15.6422 10.5353 15.3955 10.2887L14.5534 9.44659C14.435 9.32815 14.3684 9.1675 14.3684 9V7.31579C14.3684 6.96698 14.6512 6.68421 15 6.68421Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/personale.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/personale.svg new file mode 100644 index 00000000000..3d177939da0 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/personale.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9 3.75C6.92893 3.75 5.25 5.42893 5.25 7.5C5.25 9.57107 6.92893 11.25 9 11.25C11.0711 11.25 12.75 9.57107 12.75 7.5C12.75 5.42893 11.0711 3.75 9 3.75ZM3.75 7.5C3.75 4.60051 6.10051 2.25 9 2.25C11.8995 2.25 14.25 4.60051 14.25 7.5C14.25 10.3995 11.8995 12.75 9 12.75C6.10051 12.75 3.75 10.3995 3.75 7.5ZM9 15.75C7.60761 15.75 6.27225 16.3031 5.28769 17.2877C4.30312 18.2723 3.75 19.6076 3.75 21C3.75 21.4142 3.41421 21.75 3 21.75C2.58579 21.75 2.25 21.4142 2.25 21C2.25 19.2098 2.96116 17.4929 4.22703 16.227C5.4929 14.9612 7.20979 14.25 9 14.25C10.7902 14.25 12.5071 14.9612 13.773 16.227C15.0388 17.4929 15.75 19.2098 15.75 21C15.75 21.4142 15.4142 21.75 15 21.75C14.5858 21.75 14.25 21.4142 14.25 21C14.25 19.6076 13.6969 18.2723 12.7123 17.2877C11.7277 16.3031 10.3924 15.75 9 15.75ZM16.2139 4.92619C15.8169 4.80804 15.3993 5.03409 15.2812 5.4311C15.163 5.8281 15.3891 6.24572 15.7861 6.36387C16.9225 6.7021 17.75 7.75544 17.75 9.00009C17.75 10.5189 16.5188 11.7501 15 11.7501C14.5858 11.7501 14.25 12.0859 14.25 12.5001L14.25 13.5001C14.25 13.699 14.329 13.8898 14.4697 14.0304C14.6103 14.1711 14.8011 14.2501 15 14.2501C16.1272 14.2501 17.2082 14.6979 18.0052 15.4949C18.8022 16.2919 19.25 17.3729 19.25 18.5001C19.25 18.9143 19.5858 19.2501 20 19.2501C20.4142 19.2501 20.75 18.9143 20.75 18.5001C20.75 16.9751 20.1442 15.5126 19.0659 14.4342C18.3597 13.7281 17.4888 13.2246 16.5435 12.9611C18.1276 12.3434 19.25 10.8028 19.25 9.00009C19.25 7.07372 17.969 5.44851 16.2139 4.92619Z" fill="#111D46"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/post_og_arkiv.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/post_og_arkiv.svg new file mode 100644 index 00000000000..3212d875cf9 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/post_og_arkiv.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M3 5.25C2.58579 5.25 2.25 5.58579 2.25 6V19C2.25 19.4142 2.58579 19.75 3 19.75H21C21.4142 19.75 21.75 19.4142 21.75 19V6C21.75 5.58579 21.4142 5.25 21 5.25H3ZM3.75 17.1893L9.02277 11.9166L3.75 8.40139V17.1893ZM10.2956 12.7651L4.81066 18.25H19.1893L13.7044 12.7651L12.416 13.624C12.1641 13.792 11.8359 13.792 11.584 13.624L10.2956 12.7651ZM14.9772 11.9166L20.25 8.40139V17.1893L14.9772 11.9166ZM3.97708 6.75L12 12.0986L20.0229 6.75H3.97708Z" fill="#23262A"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/skatt_avgift_regnskap_og_toll.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/skatt_avgift_regnskap_og_toll.svg new file mode 100644 index 00000000000..5c8e23034bd --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/skatt_avgift_regnskap_og_toll.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0792 3.04294C9.85243 3.0071 9.69906 3.2254 9.75643 3.39752L10.415 5.37324C10.9062 5.29163 11.4339 5.24994 12 5.24994C12.5661 5.24994 13.0938 5.29163 13.585 5.37324L14.2436 3.39752C14.3009 3.2254 14.1476 3.0071 13.9208 3.04294C13.2603 3.14736 12.4973 3.24994 12 3.24994C11.5027 3.24994 10.7397 3.14736 10.0792 3.04294ZM8.3334 3.87186L8.96564 5.76857C8.54821 5.93422 8.16578 6.13831 7.81654 6.37904C6.74891 7.11495 6.05887 8.14684 5.61268 9.30451C4.79574 11.4241 4.75232 14.1047 4.75011 16.5087C4.62251 16.5735 4.47928 16.654 4.33397 16.7509C3.91368 17.0311 3.25 17.6018 3.25 18.4999C3.25 19.269 3.63792 19.8235 3.96967 20.1553C4.13758 20.3232 4.30348 20.4474 4.42772 20.5302C4.49048 20.5721 4.54435 20.6045 4.58493 20.6277C4.60527 20.6393 4.62241 20.6487 4.63583 20.6558L4.65315 20.6649L4.65963 20.6683L4.6623 20.6696L4.66349 20.6702L4.66405 20.6705C4.66432 20.6706 4.66459 20.6708 5 19.9999L4.66459 20.6708C4.76873 20.7228 4.88357 20.7499 5 20.7499H19C19.1164 20.7499 19.2313 20.7228 19.3354 20.6708L19 19.9999C19.3354 20.6708 19.3357 20.6706 19.3359 20.6705L19.3365 20.6702L19.3377 20.6696L19.3404 20.6683L19.3469 20.6649L19.3642 20.6558C19.3776 20.6487 19.3947 20.6393 19.4151 20.6277C19.4557 20.6045 19.5095 20.5721 19.5723 20.5302C19.6965 20.4474 19.8624 20.3232 20.0303 20.1553C20.3621 19.8235 20.75 19.269 20.75 18.4999C20.75 17.6018 20.0863 17.0311 19.666 16.7509C19.5207 16.654 19.3775 16.5735 19.2499 16.5087C19.2477 14.1047 19.2043 11.4241 18.3873 9.30451C17.9411 8.14684 17.2511 7.11495 16.1835 6.37904C15.8342 6.13831 15.4518 5.93422 15.0344 5.76857L15.6666 3.87186C16.0948 2.58714 14.9767 1.35741 13.6866 1.56134C13.014 1.66768 12.3625 1.74994 12 1.74994C11.6375 1.74994 10.986 1.66768 10.3134 1.56134C9.02331 1.35741 7.90516 2.58714 8.3334 3.87186ZM18.7866 19.2499C18.8418 19.2101 18.9063 19.1579 18.9697 19.0946C19.1379 18.9264 19.25 18.7309 19.25 18.4999C19.25 18.3981 19.1637 18.2188 18.834 17.999C18.6919 17.9043 18.5446 17.8301 18.4298 17.779C18.3734 17.754 18.3275 17.7357 18.2975 17.7242C18.2825 17.7185 18.2716 17.7146 18.2656 17.7125L18.2624 17.7113L18.2621 17.7112L18.2614 17.711L18.2604 17.7106C17.9555 17.6078 17.75 17.3219 17.75 16.9999C17.75 14.389 17.7396 11.7949 16.9877 9.84396C16.6214 8.89353 16.0927 8.13827 15.3322 7.61406C14.5713 7.0896 13.511 6.74994 12 6.74994C10.489 6.74994 9.42871 7.0896 8.66784 7.61406C7.90734 8.13827 7.37863 8.89353 7.01232 9.84396C6.26038 11.7949 6.25 14.389 6.25 16.9999C6.25 17.3219 6.04452 17.6078 5.7396 17.7106L5.73858 17.711L5.73794 17.7112L5.73772 17.7113L5.73441 17.7125C5.72837 17.7146 5.71751 17.7185 5.70254 17.7242C5.67248 17.7357 5.62657 17.754 5.57023 17.779C5.45542 17.8301 5.30805 17.9043 5.16603 17.999C4.83632 18.2188 4.75 18.3981 4.75 18.4999C4.75 18.7309 4.86208 18.9264 5.03033 19.0946C5.09365 19.1579 5.15816 19.2101 5.21339 19.2499H18.7866ZM11.2564 10.9054C11.5878 11.154 11.655 11.6241 11.4064 11.9554L9.84924 14.0317L11.4064 16.108C11.655 16.4393 11.5878 16.9094 11.2564 17.158C10.9251 17.4065 10.455 17.3393 10.2064 17.008L9.03018 15.4396V16.558C9.03018 16.9722 8.6944 17.308 8.28018 17.308C7.86597 17.308 7.53018 16.9722 7.53018 16.558V11.5054C7.53018 11.0912 7.86597 10.7554 8.28018 10.7554C8.6944 10.7554 9.03018 11.0912 9.03018 11.5054V12.6238L10.2064 11.0554C10.455 10.7241 10.9251 10.6569 11.2564 10.9054ZM12.8778 10.7554C12.4636 10.7554 12.1278 11.0912 12.1278 11.5054V16.558C12.1278 16.9722 12.4636 17.308 12.8778 17.308C13.292 17.308 13.6278 16.9722 13.6278 16.558V14.7817H13.9932L15.049 16.8933C15.2342 17.2638 15.6848 17.414 16.0552 17.2288C16.4257 17.0435 16.5759 16.593 16.3906 16.2225L15.5189 14.479C16.0898 14.1237 16.4698 13.4905 16.4698 12.7686C16.4698 11.6567 15.5685 10.7554 14.4567 10.7554H12.8778ZM14.4397 13.2817C14.4488 13.2815 14.4579 13.2815 14.467 13.2816C14.7456 13.2761 14.9698 13.0485 14.9698 12.7686C14.9698 12.4852 14.7401 12.2554 14.4567 12.2554H13.6278V13.2817H14.4397Z" fill="#111D46"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/transport_og_lagring.svg b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/transport_og_lagring.svg new file mode 100644 index 00000000000..7c9683690fe --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/AllAccessPackages/Icons/transport_og_lagring.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 4.25C2.80964 4.25 2.25 4.80964 2.25 5.5V16.5C2.25 17.1904 2.80964 17.75 3.5 17.75H4.75C4.75 19.1307 5.86929 20.25 7.25 20.25C8.63071 20.25 9.75 19.1307 9.75 17.75H14.75C14.75 19.1307 15.8693 20.25 17.25 20.25C18.6307 20.25 19.75 19.1307 19.75 17.75H20.5C21.1904 17.75 21.75 17.1904 21.75 16.5V12.8556L19.8221 8.03576C19.6322 7.56119 19.1726 7.25 18.6615 7.25H15.75V5.5C15.75 4.80964 15.1904 4.25 14.5 4.25H3.5ZM7.25 15.25C8.06791 15.25 8.79408 15.6428 9.25018 16.25H14.25V5.75H3.75V16.25H5.24982C5.70592 15.6428 6.43209 15.25 7.25 15.25ZM17.25 15.25C16.6872 15.25 16.1678 15.436 15.75 15.7498V8.75H18.4922L20.25 13.1444V16.25H19.2502C18.7941 15.6428 18.0679 15.25 17.25 15.25ZM17.25 16.75C16.6977 16.75 16.25 17.1977 16.25 17.75C16.25 18.3023 16.6977 18.75 17.25 18.75C17.8023 18.75 18.25 18.3023 18.25 17.75C18.25 17.1977 17.8023 16.75 17.25 16.75ZM6.25 17.75C6.25 17.1977 6.69772 16.75 7.25 16.75C7.80228 16.75 8.25 17.1977 8.25 17.75C8.25 18.3023 7.80228 18.75 7.25 18.75C6.69772 18.75 6.25 18.3023 6.25 17.75Z" fill="#111D46"/> +</svg> diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/PolicyAccessPackages.tsx b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/PolicyAccessPackages.tsx index 416dde5b131..8dbf93ec3e1 100644 --- a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/PolicyAccessPackages.tsx +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/PolicyAccessPackages.tsx @@ -1,11 +1,6 @@ import React, { type ReactElement, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { - StudioAlert, - StudioLabelAsParagraph, - StudioParagraph, - StudioTextfield, -} from '@studio/components'; +import { StudioLabelAsParagraph, StudioTextfield } from '@studio/components'; import { getUpdatedRules } from '../../../../utils/PolicyRuleUtils'; import { usePolicyEditorContext } from '../../../../contexts/PolicyEditorContext'; import { usePolicyRuleContext } from '../../../../contexts/PolicyRuleContext'; @@ -15,8 +10,9 @@ import { groupAccessPackagesByArea, isAccessPackageSelected, } from './policyAccessPackageUtils'; -import { ChosenAccessPackages } from './ChosenAccessPackages/ChosenAccessPackages'; -import { AllAccessPackages } from './AllAccessPackages/AllAccessPackages'; +import { ChosenAccessPackages } from './ChosenAccessPackages'; +import { AllAccessPackages } from './AllAccessPackages'; +import { PolicyAccessPackagesWarning } from './PolicyAccessPackagesWarning'; export const PolicyAccessPackages = (): ReactElement => { const { t } = useTranslation(); @@ -78,17 +74,10 @@ export const PolicyAccessPackages = (): ReactElement => { return ( <div className={classes.accessPackages}> - <StudioAlert severity='warning' size='sm'> - <StudioLabelAsParagraph size='md' spacing> - {t('policy_editor.access_package_warning_header')} - </StudioLabelAsParagraph> - <StudioParagraph size='sm'> - {t('policy_editor.access_package_warning_body')} - </StudioParagraph> - </StudioAlert> <StudioLabelAsParagraph size='md' spacing> {t('policy_editor.access_package_header')} </StudioLabelAsParagraph> + <PolicyAccessPackagesWarning /> <ChosenAccessPackages chosenAccessPackages={chosenAccessPackages} groupedAccessPackagesByArea={groupedAccessPackagesByArea} diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/PolicyAccessPackagesWarning.tsx b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/PolicyAccessPackagesWarning.tsx new file mode 100644 index 00000000000..c4d962c9bf0 --- /dev/null +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyAccessPackages/PolicyAccessPackagesWarning.tsx @@ -0,0 +1,44 @@ +import React, { type ReactElement } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { + StudioAlert, + StudioLabelAsParagraph, + StudioParagraph, + StudioLink, + StudioList, +} from '@studio/components'; + +export const PolicyAccessPackagesWarning = (): ReactElement => { + const { t } = useTranslation(); + return ( + <StudioAlert severity='warning' size='sm'> + <StudioLabelAsParagraph size='md' spacing> + {t('policy_editor.access_package_warning_header')} + </StudioLabelAsParagraph> + <StudioParagraph size='sm' spacing> + <Trans i18nKey='policy_editor.access_package_warning_body1'> + <StudioLink + href={'https://docs.altinn.studio/nb/authorization/what-do-you-get/accessgroups/'} + target='_newTab' + rel='noopener noreferrer' + > + {''} + </StudioLink> + </Trans> + </StudioParagraph> + <StudioParagraph size='sm' spacing> + {t('policy_editor.access_package_warning_body2')} + </StudioParagraph> + <StudioLabelAsParagraph size='sm'> + {t('policy_editor.access_package_warning_header2')} + </StudioLabelAsParagraph> + <StudioList.Root size='sm'> + <StudioList.Unordered> + <StudioList.Item>{t('policy_editor.access_package_warning_listitem1')}</StudioList.Item> + <StudioList.Item>{t('policy_editor.access_package_warning_listitem2')}</StudioList.Item> + <StudioList.Item>{t('policy_editor.access_package_warning_listitem3')}</StudioList.Item> + </StudioList.Unordered> + </StudioList.Root> + </StudioAlert> + ); +}; diff --git a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyRule.tsx b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyRule.tsx index 4a306fc9e79..ccc93086d01 100644 --- a/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyRule.tsx +++ b/frontend/packages/policy-editor/src/components/PolicyCardRules/PolicyRule/PolicyRule.tsx @@ -13,7 +13,6 @@ import { getNewRuleId } from '../../../utils'; import { usePolicyEditorContext } from '../../../contexts/PolicyEditorContext'; import { ObjectUtils } from '@studio/pure-functions'; import { PolicyAccessPackages } from './PolicyAccessPackages'; -import { FeatureFlag, shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils'; export type PolicyRuleProps = { policyRule: PolicyRuleCard; @@ -85,7 +84,7 @@ export const PolicyRule = ({ <SubResources /> <PolicyActions /> <PolicySubjects /> - {shouldDisplayFeature(FeatureFlag.AccessPackages) && <PolicyAccessPackages />} + <PolicyAccessPackages /> <PolicyDescription /> </ExpandablePolicyElement> {showErrors && <PolicyRuleErrorMessage />} diff --git a/frontend/packages/policy-editor/src/utils/index.ts b/frontend/packages/policy-editor/src/utils/index.ts index 33b9eec82bc..3e445c5ae4c 100644 --- a/frontend/packages/policy-editor/src/utils/index.ts +++ b/frontend/packages/policy-editor/src/utils/index.ts @@ -64,7 +64,7 @@ export const mapPolicyRulesBackendObjectToPolicyRuleCard = ( actions: r.actions, description: r.description, subject: subjectIds, - accessPackages: r.accessPackages, + accessPackages: r.accessPackages || [], resources: mappedResources, }; }); diff --git a/frontend/packages/process-editor/package.json b/frontend/packages/process-editor/package.json index 5027530feb7..3effd90d03b 100644 --- a/frontend/packages/process-editor/package.json +++ b/frontend/packages/process-editor/package.json @@ -15,7 +15,7 @@ "peerDependencies": { "react": "18.3.1", "react-dom": "18.3.1", - "typescript": "5.7.2" + "typescript": "5.7.3" }, "dependencies": { "bpmn-js": "^13.2.2" diff --git a/frontend/packages/process-editor/src/bpmnProviders/SupportedContextPadProvider.js b/frontend/packages/process-editor/src/bpmnProviders/SupportedContextPadProvider.js index 0971159f330..b124370dec0 100644 --- a/frontend/packages/process-editor/src/bpmnProviders/SupportedContextPadProvider.js +++ b/frontend/packages/process-editor/src/bpmnProviders/SupportedContextPadProvider.js @@ -16,7 +16,7 @@ class SupportedContextPadProvider { } const isConfirmed = confirm( - 'Prosess-steget du vil slette kan være knyttet til en sidegruppe. Den kan inneholde visningsoppsett eller skjema du har satt opp. Hvis du sletter steget, sletter du også hele sidegruppen og alt som hører til.', + 'Prosess-steget du vil slette kan være knyttet til en sidegruppe. Den kan inneholde visningsoppsett eller skjema du har satt opp. Hvis du sletter steget, sletter du også hele sidegruppen og alt som hører til.\nAlle Summary2-komponenter knyttet til dette prosess-steget vil også bli slettet.', ); if (isConfirmed) { diff --git a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx index 4d632932795..23e3bd0a8a8 100644 --- a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx +++ b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx @@ -6,9 +6,7 @@ import { BpmnConfigPanelFormContextProvider } from '../../../contexts/BpmnConfig import { textMock } from '@studio/testing/mocks/i18nMock'; jest.mock('../../../hooks/useBpmnEditor', () => ({ - useBpmnEditor: jest.fn().mockReturnValue({ - canvasRef: { current: document.createElement('div') }, - }), + useBpmnEditor: jest.fn().mockReturnValue(jest.fn()), })); describe('BPMNEditor', () => { diff --git a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx index 6a3b9bc2a06..e117469ea80 100644 --- a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx +++ b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx @@ -4,7 +4,7 @@ import { useBpmnEditor } from '../../../hooks/useBpmnEditor'; import './BPMNEditor.css'; export const BPMNEditor = (): React.ReactElement => { - const { canvasRef } = useBpmnEditor(); + const canvasRef = useBpmnEditor(); - return <div className={classes.editorContainer} ref={canvasRef}></div>; + return <div className={classes.editorContainer} ref={canvasRef} />; }; diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/ActionsEditor/ActionsEditor.module.css b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/ActionsEditor/ActionsEditor.module.css index e51b586278b..33b9f1ce97f 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/ActionsEditor/ActionsEditor.module.css +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/ActionsEditor/ActionsEditor.module.css @@ -29,3 +29,8 @@ .actionView { margin-bottom: var(--fds-spacing-3); } + +.container { + margin: 0 var(--fds-spacing-3); + width: inherit; +} diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/ActionsEditor/ActionsEditor.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/ActionsEditor/ActionsEditor.tsx index e428cb4a312..19eb2d3ffad 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/ActionsEditor/ActionsEditor.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/ActionsEditor/ActionsEditor.tsx @@ -91,7 +91,7 @@ const ActionEditable = ({ !getPredefinedActions(bpmnDetails.taskType).includes(actionElement.action); return ( - <StudioCard> + <StudioCard className={classes.container}> <StudioCard.Header className={classes.cardHeader}> <StudioParagraph size='small'> {t('process_editor.configuration_panel_actions_action_card_title', { diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.module.css b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.module.css deleted file mode 100644 index 6fdbe9e7fc5..00000000000 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.container { - margin: 0 0 var(--fds-spacing-3) var(--fds-spacing-3); -} diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.tsx index 27cfd2ba6e4..bf7cfb4fc44 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.tsx @@ -7,7 +7,6 @@ import { useChecksum } from './useChecksum'; import { ActionsEditor } from './ActionsEditor'; import { useBpmnContext } from '../../../../contexts/BpmnContext'; import { type Action, BpmnActionModeler } from '../../../../utils/bpmnModeler/BpmnActionModeler'; -import classes from './EditActions.module.css'; export const EditActions = (): React.ReactElement => { const { t } = useTranslation(); @@ -49,19 +48,17 @@ export const EditActions = (): React.ReactElement => { return ( <> {actions.map((actionElement: ModdleElement, index: number) => ( - <div key={getUniqueKey(index)} className={classes.container}> - <ActionsEditor - actionElement={actionElement} - actionIndex={index} - mode={!actionElement.action ? 'edit' : 'view'} - onDeleteClick={() => onDeleteActionItemSideEffect(index)} - /> - </div> + <ActionsEditor + key={getUniqueKey(index)} + actionElement={actionElement} + actionIndex={index} + mode={!actionElement.action ? 'edit' : 'view'} + onDeleteClick={() => onDeleteActionItemSideEffect(index)} + /> ))} <StudioProperty.Button onClick={onNewActionAddClicked} property={t('process_editor.configuration_panel_actions_add_new')} - size='small' /> </> ); diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypes/EditDataTypes.module.css b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypes/EditDataTypes.module.css deleted file mode 100644 index 5c04a2a3501..00000000000 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypes/EditDataTypes.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.definedValue { - align-items: center; - display: flex; - gap: var(--fds-spacing-1); -} diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypes/EditDataTypes.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypes/EditDataTypes.tsx index 4a93d1a569b..8a20f209a64 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypes/EditDataTypes.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypes/EditDataTypes.tsx @@ -3,7 +3,6 @@ import { StudioProperty } from '@studio/components'; import { useTranslation } from 'react-i18next'; import { LinkIcon } from '@studio/icons'; import { SelectDataTypes } from './SelectDataTypes'; -import classes from './EditDataTypes.module.css'; import { useBpmnContext } from '../../../../contexts/BpmnContext'; export type EditDataTypesProps = { @@ -27,19 +26,12 @@ export const EditDataTypes = ({ setDataModelSelectVisible(false); }, [bpmnDetails.id]); - const definedValueWithLinkIcon = ( - <span className={classes.definedValue}> - <LinkIcon /> {existingDataTypeForTask} - </span> - ); - return ( <> {!existingDataTypeForTask && !dataModelSelectVisible ? ( <StudioProperty.Button onClick={() => setDataModelSelectVisible(true)} property={t('process_editor.configuration_panel_set_data_model_link')} - size='small' icon={<LinkIcon />} /> ) : dataModelSelectVisible ? ( @@ -55,9 +47,10 @@ export const EditDataTypes = ({ aria-label={t('process_editor.configuration_panel_set_data_model', { dataModelName: existingDataTypeForTask, })} + icon={<LinkIcon />} onClick={() => setDataModelSelectVisible(true)} property={t('process_editor.configuration_panel_set_data_model_label')} - value={definedValueWithLinkIcon} + value={existingDataTypeForTask} /> )} </> diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypesToSign/EditDataTypesToSign.module.css b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypesToSign/EditDataTypesToSign.module.css deleted file mode 100644 index 15a61bbfa90..00000000000 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypesToSign/EditDataTypesToSign.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.dataType { - align-items: center; - display: flex; - gap: var(--fds-spacing-1); -} diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypesToSign/EditDataTypesToSign.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypesToSign/EditDataTypesToSign.tsx index 5dce58d8741..a5f0b506391 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypesToSign/EditDataTypesToSign.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditDataTypesToSign/EditDataTypesToSign.tsx @@ -3,7 +3,6 @@ import { StudioProperty } from '@studio/components'; import { useTranslation } from 'react-i18next'; import { LinkIcon } from '@studio/icons'; import { SelectDataTypesToSign } from './SelectDataTypesToSign'; -import classes from './EditDataTypesToSign.module.css'; import { useGetDataTypesToSign } from '../../../../hooks/dataTypesToSign/useGetDataTypesToSign'; export const EditDataTypesToSign = () => { @@ -21,15 +20,8 @@ export const EditDataTypesToSign = () => { onClick={() => setDataTypesToSignSelectVisible(true)} property={t('process_editor.configuration_panel_set_data_types_to_sign')} title={t('process_editor.configuration_panel_set_data_types_to_sign')} - value={ - <> - {selectedDataTypes?.map((dataType) => ( - <div key={dataType} className={classes.dataType}> - <LinkIcon /> {dataType} - </div> - ))} - </> - } + icon={<LinkIcon />} + value={selectedDataTypes?.map((dataType: string) => <div key={dataType}>{dataType}</div>)} /> ); }; diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditUniqueFromSignaturesInDataTypes/EditUniqueFromSignaturesInDataTypes.module.css b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditUniqueFromSignaturesInDataTypes/EditUniqueFromSignaturesInDataTypes.module.css deleted file mode 100644 index 15a61bbfa90..00000000000 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditUniqueFromSignaturesInDataTypes/EditUniqueFromSignaturesInDataTypes.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.dataType { - align-items: center; - display: flex; - gap: var(--fds-spacing-1); -} diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditUniqueFromSignaturesInDataTypes/EditUniqueFromSignaturesInDataTypes.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditUniqueFromSignaturesInDataTypes/EditUniqueFromSignaturesInDataTypes.tsx index c95a559cb31..655f073ad00 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditUniqueFromSignaturesInDataTypes/EditUniqueFromSignaturesInDataTypes.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditUniqueFromSignaturesInDataTypes/EditUniqueFromSignaturesInDataTypes.tsx @@ -4,7 +4,6 @@ import { StudioProperty } from '@studio/components'; import { useTranslation } from 'react-i18next'; import { PersonPencilIcon } from '@studio/icons'; import { SelectUniqueFromSignaturesInDataTypes } from './SelectUniqueFromSignaturesInDataTypes'; -import classes from './EditUniqueFromSignaturesInDataTypes.module.css'; import { getSelectedDataTypes } from './UniqueFromSignaturesInDataTypesUtils'; import { StudioModeler } from '../../../../utils/bpmnModeler/StudioModeler'; @@ -51,7 +50,6 @@ export const EditUniqueFromSignaturesInDataTypes = () => { property={t( 'process_editor.configuration_panel_set_unique_from_signatures_in_data_types_link', )} - size='small' icon={<PersonPencilIcon />} /> ) : isSelectVisible ? ( @@ -63,15 +61,8 @@ export const EditUniqueFromSignaturesInDataTypes = () => { 'process_editor.configuration_panel_set_unique_from_signatures_in_data_types', )} title={t('process_editor.configuration_panel_set_unique_from_signatures_in_data_types')} - value={ - <> - {signingTasks?.map((dataType) => ( - <div key={dataType.id} className={classes.dataType}> - <PersonPencilIcon /> {dataType.name} - </div> - ))} - </> - } + icon={<PersonPencilIcon />} + value={signingTasks?.map((dataType) => <div key={dataType.id}>{dataType.name}</div>)} /> )} </> diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CreateCustomReceiptForm/CreateCustomReceiptForm.test.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CreateCustomReceiptForm/CreateCustomReceiptForm.test.tsx index bc6040f73de..b8c19b2be44 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CreateCustomReceiptForm/CreateCustomReceiptForm.test.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CreateCustomReceiptForm/CreateCustomReceiptForm.test.tsx @@ -194,7 +194,7 @@ describe('CreateCustomReceiptForm', () => { await user.type(inputField, invalidFormatLayoutSetName); - const error = screen.getByText(textMock('validation_errors.file_name_invalid')); + const error = screen.getByText(textMock('validation_errors.name_invalid')); expect(error).toBeInTheDocument(); const button = screen.getByRole('button', { diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.test.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.test.tsx index 7dfe88f4107..45cf905a4e9 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.test.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.test.tsx @@ -29,7 +29,7 @@ const layoutSetWithDataTask: LayoutSetConfig = { const layoutSetIdTextKeys: Record<string, string> = { [emptyLayoutSetName]: 'validation_errors.required', - [invalidFormatLayoutSetName]: 'validation_errors.file_name_invalid', + [invalidFormatLayoutSetName]: 'validation_errors.name_invalid', [existingLayoutSetName]: 'process_editor.configuration_panel_layout_set_id_not_unique', }; diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceiptContent.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceiptContent.tsx index 4c4173d34c7..032ce00656e 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceiptContent.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceiptContent.tsx @@ -27,7 +27,6 @@ export const CustomReceiptContent = (): React.ReactElement => { return ( <StudioProperty.Button onClick={openCustomReceiptFields} - size='small' property={t('process_editor.configuration_panel_custom_receipt_create_your_own_button')} className={classes.createButton} /> diff --git a/frontend/packages/process-editor/src/hooks/useBpmnEditor.test.tsx b/frontend/packages/process-editor/src/hooks/useBpmnEditor.test.tsx index 6d99b251773..f6a37813303 100644 --- a/frontend/packages/process-editor/src/hooks/useBpmnEditor.test.tsx +++ b/frontend/packages/process-editor/src/hooks/useBpmnEditor.test.tsx @@ -1,18 +1,37 @@ import React from 'react'; -import { renderHook, waitFor } from '@testing-library/react'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook, waitFor, act } from '@testing-library/react'; +import type { UseBpmnEditorResult } from './useBpmnEditor'; import { useBpmnEditor } from './useBpmnEditor'; +import type { BpmnContextProviderProps } from '../contexts/BpmnContext'; import { BpmnContextProvider, useBpmnContext } from '../contexts/BpmnContext'; +import type { BpmnApiContextProps } from '../contexts/BpmnApiContext'; import { BpmnApiContextProvider } from '../contexts/BpmnApiContext'; -import { useBpmnModeler } from './useBpmnModeler'; -import type { BpmnDetails } from '../types/BpmnDetails'; import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse'; -import { getMockBpmnElementForTask, mockBpmnDetails } from '../../test/mocks/bpmnDetailsMock'; -import { mockModelerRef } from '../../test/mocks/bpmnModelerMock'; -import { getBpmnEditorDetailsFromBusinessObject } from '../utils/bpmnObjectBuilders'; +import { mockBpmnDetails } from '../../test/mocks/bpmnDetailsMock'; import { StudioRecommendedNextActionContextProvider } from '@studio/components'; +import { BpmnConfigPanelFormContextProvider } from '../contexts/BpmnConfigPanelContext'; +import type { TaskEvent } from '../types/TaskEvent'; +import { EventListeners } from '../../test/EventListeners'; +import type { + BpmnBusinessObjectEditor, + BpmnExtensionElementsEditor, +} from '@altinn/process-editor/types/BpmnBusinessObjectEditor'; +import { BpmnTypeEnum } from '../enum/BpmnTypeEnum'; +import type { BpmnTaskType } from '../types/BpmnTaskType'; +import type { OnProcessTaskEvent } from '@altinn/process-editor/types/OnProcessTask'; +import type { BpmnDetails } from '@altinn/process-editor/types/BpmnDetails'; +import type { SelectionChangedEvent } from '@altinn/process-editor/types/SelectionChangeEvent'; +import type BpmnModeler from 'bpmn-js/lib/Modeler'; +// Test data: +const appLibVersion = '8.0.0'; +const defaultBpmnContextProps: Omit<BpmnContextProviderProps, 'children'> = { + appLibVersion, + bpmnXml: undefined, +}; const layoutSetId = 'someLayoutSetId'; -const layoutSetsMock: LayoutSets = { +const layoutSets: LayoutSets = { sets: [ { id: layoutSetId, @@ -20,156 +39,229 @@ const layoutSetsMock: LayoutSets = { }, ], }; +const defaultBpmnApiContextProps: BpmnApiContextProps = { + availableDataTypeIds: [], + availableDataModelIds: [], + allDataModelIds: [], + layoutSets, + pendingApiOperations: false, + existingCustomReceiptLayoutSetId: undefined, + addLayoutSet: jest.fn(), + deleteLayoutSet: jest.fn(), + mutateLayoutSetId: jest.fn(), + mutateDataTypes: jest.fn(), + saveBpmn: jest.fn(), + openPolicyEditor: jest.fn(), + onProcessTaskAdd: jest.fn(), + onProcessTaskRemove: jest.fn(), +}; +const taskType: BpmnTaskType = 'data'; +const extensionElements: BpmnExtensionElementsEditor = { + values: [ + { + $type: 'altinn:TaskExtension', + taskType, + }, + ], +}; +const businessObject: BpmnBusinessObjectEditor = { + $type: BpmnTypeEnum.Task, + id: 'test', + extensionElements, +}; +const element: TaskEvent['element'] = { + id: 'test', + businessObject, +}; +const xml = '<testxml></testxml>'; -class BpmnModelerMockImpl { - public readonly _currentEventName: string; - public readonly _currentEvent: any; - private readonly eventBus: any; - - constructor(currentEventName: string, currentEvent) { - this._currentEventName = currentEventName; - this._currentEvent = currentEvent; - this.eventBus = { - _currentEventName: this._currentEventName, - on: this.on, - }; - } - - on(eventName: string, listener: (event: any) => void) { - if (eventName === this._currentEventName) { - listener(this._currentEvent); - } - } - - get(elementName: string) { - if (elementName === 'eventBus') { - return this.eventBus; - } - } -} - -jest.mock('../utils/bpmnObjectBuilders', () => ({ - getBpmnEditorDetailsFromBusinessObject: jest.fn().mockReturnValue({}), -})); - -jest.mock('../contexts/BpmnConfigPanelContext', () => ({ - useBpmnConfigPanelFormContext: jest.fn(() => ({ - metadataFormRef: { current: null }, - resetForm: jest.fn(), - })), -})); +// Mocks: +jest.mock('bpmn-js/lib/Modeler', () => jest.fn().mockImplementation(bpmnModelerImplementation)); -jest.mock('../contexts/BpmnContext', () => ({ - ...jest.requireActual('../contexts/BpmnContext'), - useBpmnContext: jest.fn(() => ({ - getUpdatedXml: jest.fn(), - modelerRef: { current: null }, - setBpmnDetails: jest.fn(), - })), -})); +function bpmnModelerImplementation(): BpmnModeler { + return { + get: getModeler, + importXML, + on, + off, + saveXML, + attachTo: jest.fn(), + clear: jest.fn(), + createDiagram: jest.fn(), + destroy: jest.fn(), + detach: jest.fn(), + getDefinitions: jest.fn(), + getModules: jest.fn(), + importDefinitions: jest.fn(), + invoke: jest.fn(), + open: jest.fn(), + saveSVG: jest.fn(), + }; +} -jest.mock('./useBpmnModeler', () => ({ - useBpmnModeler: jest.fn().mockReturnValue({}), +const getModeler = jest.fn().mockImplementation(() => ({ + zoom: () => {}, })); - -const setBpmnDetailsMock = jest.fn(); -const onProcessTaskAddMock = jest.fn(); -const onProcessTaskRemoveMock = jest.fn(); - -const overrideUseBpmnContext = () => { - (useBpmnContext as jest.Mock).mockReturnValue({ - getUpdatedXml: jest.fn(), - modelerRef: { - ...mockModelerRef, - }, - setBpmnDetails: setBpmnDetailsMock, +const importXML = jest.fn().mockImplementation(() => Promise.resolve({ warnings: [] })); +const on = jest + .fn() + .mockImplementation(<K extends keyof EventMap>(eventName: K, callback: EventMap[K]): void => { + eventListeners.add(eventName, callback); }); -}; - -const overrideUseBpmnModeler = (currentEventName: string, currentEvent: any) => { - (useBpmnModeler as jest.Mock).mockReturnValue({ - getModeler: () => new BpmnModelerMockImpl(currentEventName, currentEvent), - destroyModeler: jest.fn(), +const off = jest + .fn() + .mockImplementation(<K extends keyof EventMap>(eventName: K, callback: EventMap[K]): void => { + eventListeners.remove(eventName, callback); }); -}; +const saveXML = jest.fn().mockImplementation(() => Promise.resolve({ xml })); -const overrideGetBpmnEditorDetailsFromBusinessObject = (bpmnDetails: BpmnDetails) => { - (getBpmnEditorDetailsFromBusinessObject as jest.Mock).mockReturnValue(bpmnDetails); -}; +const eventListeners = new EventListeners<EventMap>(); -const wrapper = ({ children }) => ( - <BpmnContextProvider appLibVersion={'8.0.0'}> - <BpmnApiContextProvider - addLayoutSet={jest.fn()} - deleteLayoutSet={jest.fn()} - saveBpmn={saveBpmnMock} - onProcessTaskAdd={onProcessTaskAddMock} - onProcessTaskRemove={onProcessTaskRemoveMock} - layoutSets={layoutSetsMock} - > - <StudioRecommendedNextActionContextProvider> - {children} - </StudioRecommendedNextActionContextProvider> - </BpmnApiContextProvider> - </BpmnContextProvider> -); - -const saveBpmnMock = jest.fn(); +type EventMap = { + ['commandStack.changed']: () => void; + ['shape.added']: (taskEvent: TaskEvent) => void; + ['shape.remove']: (taskEvent: TaskEvent) => void; + ['selection.changed']: (selectionChangedEvent: SelectionChangedEvent) => void; +}; describe('useBpmnEditor', () => { - afterEach(() => { + beforeEach(() => { jest.clearAllMocks(); + eventListeners.clear(); }); - it('should call saveBpmn when "commandStack.changed" event is triggered on modelerInstance', async () => { - const currentEventName = 'commandStack.changed'; - const currentEvent = { element: getMockBpmnElementForTask('data') }; - renderUseBpmnEditor(false, currentEventName, currentEvent); - - await waitFor(() => expect(saveBpmnMock).toHaveBeenCalledTimes(1)); + it('Calls saveBpmn with correct data when the "commandStack.changed" event is triggered', async () => { + const saveBpmn = jest.fn(); + await setup({ bpmnApiContextProps: { saveBpmn } }); + eventListeners.triggerEvent('commandStack.changed'); + await waitFor(expect(saveBpmn).toHaveBeenCalled); + expect(saveBpmn).toHaveBeenCalledTimes(1); + expect(saveBpmn).toHaveBeenCalledWith(xml, null); }); - it('should handle "shape.added" event', async () => { - const currentEvent = { element: getMockBpmnElementForTask('data') }; - renderUseBpmnEditor(false, 'shape.added', currentEvent); + it('Calls onProcessTaskAdd with correct data when the "shape.added" event is triggered', async () => { + const onProcessTaskAdd = jest.fn(); + const taskEvent: TaskEvent = { element } as TaskEvent; + await setup({ bpmnApiContextProps: { onProcessTaskAdd } }); - await waitFor(() => expect(onProcessTaskAddMock).toHaveBeenCalledTimes(1)); + act(() => eventListeners.triggerEvent('shape.added', taskEvent)); // Need to use act here because this event also triggers the addAction function from useStudioRecommendedNextActionContext, which in turn triggers another state update + await waitFor(expect(onProcessTaskAdd).toHaveBeenCalled); + + const expectedInput: OnProcessTaskEvent = { taskEvent, taskType }; + expect(onProcessTaskAdd).toHaveBeenCalledTimes(1); + expect(onProcessTaskAdd).toHaveBeenCalledWith(expectedInput); }); - it('should handle "shape.remove" event', async () => { - const currentEvent = { element: getMockBpmnElementForTask('data') }; - renderUseBpmnEditor(false, 'shape.remove', currentEvent); + it('Calls onProcessTaskRemove with correct data when the "shape.remove" event is triggered', async () => { + const onProcessTaskRemove = jest.fn(); + const taskEvent: TaskEvent = { element } as TaskEvent; + await setup({ bpmnApiContextProps: { onProcessTaskRemove } }); + + eventListeners.triggerEvent('shape.remove', taskEvent); + await waitFor(expect(onProcessTaskRemove).toHaveBeenCalled); - await waitFor(() => expect(onProcessTaskRemoveMock).toHaveBeenCalledTimes(1)); + const expectedInput: OnProcessTaskEvent = { taskEvent, taskType }; + expect(onProcessTaskRemove).toHaveBeenCalledTimes(1); + expect(onProcessTaskRemove).toHaveBeenCalledWith(expectedInput); }); - it('should call setBpmnDetails with selected object when "selection.changed" event is triggered with new selection', async () => { - const currentEventName = 'selection.changed'; - const currentEvent = { newSelection: [getMockBpmnElementForTask('data')], oldSelection: [] }; - renderUseBpmnEditor(true, currentEventName, currentEvent); + it('Updates BPMN details with selected object when "selection.changed" event is triggered with new selection', async () => { + const selectionChangedEvent: SelectionChangedEvent = { + oldSelection: [], + newSelection: [element], + }; + const { result } = await setupWithBpmnDetails(); + act(() => eventListeners.triggerEvent('selection.changed', selectionChangedEvent)); + expect(result.current.bpmnDetails.element).toEqual(element); + }); - await waitFor(() => expect(setBpmnDetailsMock).toHaveBeenCalledTimes(1)); - expect(setBpmnDetailsMock).toHaveBeenCalledWith(expect.objectContaining(mockBpmnDetails)); + it('Updates BPMN details with null when "selection.changed" event is triggered with no new selected object', async () => { + const selectionChangedEvent: SelectionChangedEvent = { + oldSelection: [element], + newSelection: [], + }; + const { result } = await setupWithBpmnDetails(); + act(() => eventListeners.triggerEvent('selection.changed', selectionChangedEvent)); + expect(result.current.bpmnDetails).toBe(null); }); - it('should call setBpmnDetails with null when "selection.changed" event is triggered with no new selected object', async () => { - const currentEventName = 'selection.changed'; - const currentEvent = { oldSelection: [getMockBpmnElementForTask('data')], newSelection: [] }; - renderUseBpmnEditor(true, currentEventName, currentEvent); + // Todo: Remove skip when this test passes. Fixing this will resolve https://github.com/Altinn/altinn-studio/issues/13035. + it.skip('Calls only the most recent saveBpmn function when the "commandStack.changed" event is triggered', async () => { + const saveBpmn1 = jest.fn(); + const saveBpmn2 = jest.fn(); + const bpmnApiContextProps: Partial<BpmnApiContextProps> = { + saveBpmn: saveBpmn1, + }; - await waitFor(() => expect(setBpmnDetailsMock).toHaveBeenCalledTimes(1)); - expect(setBpmnDetailsMock).toHaveBeenCalledWith(null); + const { rerender } = await setup({ bpmnApiContextProps }); + bpmnApiContextProps.saveBpmn = saveBpmn2; + rerender(); + + eventListeners.triggerEvent('commandStack.changed'); + await waitFor(expect(saveBpmn2).toHaveBeenCalled); + expect(saveBpmn1).not.toHaveBeenCalled(); + expect(saveBpmn2).toHaveBeenCalledTimes(1); }); }); -const renderUseBpmnEditor = ( - overrideBpmnContext: boolean, - currentEventName: string, - currentEvent: any, - bpmnDetails = mockBpmnDetails, -) => { - overrideBpmnContext && overrideUseBpmnContext(); - overrideGetBpmnEditorDetailsFromBusinessObject(bpmnDetails); - overrideUseBpmnModeler(currentEventName, currentEvent); +type BpmnProviderProps = { + bpmnApiContextProps: Partial<BpmnApiContextProps>; +}; + +async function setup( + props?: Partial<BpmnProviderProps>, +): Promise<RenderHookResult<UseBpmnEditorResult, void>> { + const utils = renderUseBpmnEditor(props); + const { result } = utils; + const div: HTMLDivElement = document.createElement('div'); + result.current(div); + await waitFor(expect(getModeler).toHaveBeenCalled); + return utils; +} + +function renderUseBpmnEditor( + props: Partial<BpmnProviderProps> = {}, +): RenderHookResult<UseBpmnEditorResult, void> { + const wrapper = ({ children }) => renderWithBpmnProviders(children, props); return renderHook(() => useBpmnEditor(), { wrapper }); +} + +function renderWithBpmnProviders( + children: React.ReactNode, + props: Partial<BpmnProviderProps> = {}, +): React.ReactElement { + return ( + <BpmnContextProvider {...defaultBpmnContextProps}> + <BpmnConfigPanelFormContextProvider> + <BpmnApiContextProvider {...defaultBpmnApiContextProps} {...props?.bpmnApiContextProps}> + <StudioRecommendedNextActionContextProvider> + {children} + </StudioRecommendedNextActionContextProvider> + </BpmnApiContextProvider> + </BpmnConfigPanelFormContextProvider> + </BpmnContextProvider> + ); +} + +async function setupWithBpmnDetails(): Promise< + RenderHookResult<UseBpmnEditorAndDetailsResult, void> +> { + const wrapper = ({ children }) => renderWithBpmnProviders(children); + const utils = renderHook(() => useBpmnEditorAndDetails(), { wrapper }); + const { result } = utils; + const div = document.createElement('div'); + result.current.bpmnEditor(div); + await waitFor(expect(getModeler).toHaveBeenCalled); + return utils; +} + +type UseBpmnEditorAndDetailsResult = { + bpmnEditor: UseBpmnEditorResult; + bpmnDetails: BpmnDetails; +}; + +const useBpmnEditorAndDetails = (): UseBpmnEditorAndDetailsResult => { + const bpmnEditor = useBpmnEditor(); + const { bpmnDetails } = useBpmnContext(); + return { bpmnEditor, bpmnDetails }; }; diff --git a/frontend/packages/process-editor/src/hooks/useBpmnEditor.ts b/frontend/packages/process-editor/src/hooks/useBpmnEditor.ts index cf6c51b7ea0..e994a617839 100644 --- a/frontend/packages/process-editor/src/hooks/useBpmnEditor.ts +++ b/frontend/packages/process-editor/src/hooks/useBpmnEditor.ts @@ -1,7 +1,6 @@ -import { type MutableRefObject, useEffect, useCallback, useRef } from 'react'; -import type BpmnModeler from 'bpmn-js/lib/Modeler'; +import { useEffect, useCallback } from 'react'; import { useBpmnContext } from '../contexts/BpmnContext'; -import { useBpmnModeler } from './useBpmnModeler'; +import { BpmnModelerInstance } from '../utils/bpmnModeler/BpmnModelerInstance'; import { useBpmnConfigPanelFormContext } from '../contexts/BpmnConfigPanelContext'; import { useBpmnApiContext } from '../contexts/BpmnApiContext'; import type { TaskEvent } from '../types/TaskEvent'; @@ -11,16 +10,11 @@ import { useStudioRecommendedNextActionContext } from '@studio/components'; // Wrapper around bpmn-js to Reactify it -type UseBpmnViewerResult = { - canvasRef: MutableRefObject<HTMLDivElement>; - modelerRef: MutableRefObject<BpmnModeler>; -}; +export type UseBpmnEditorResult = (div: HTMLDivElement) => void; -export const useBpmnEditor = (): UseBpmnViewerResult => { +export const useBpmnEditor = (): UseBpmnEditorResult => { const { getUpdatedXml, bpmnXml, modelerRef, setBpmnDetails } = useBpmnContext(); - const canvasRef = useRef<HTMLDivElement | null>(null); const { metadataFormRef, resetForm } = useBpmnConfigPanelFormContext(); - const { getModeler, destroyModeler } = useBpmnModeler(); const { addAction } = useStudioRecommendedNextActionContext(); const { saveBpmn, onProcessTaskAdd, onProcessTaskRemove } = useBpmnApiContext(); @@ -98,14 +92,10 @@ export const useBpmnEditor = (): UseBpmnViewerResult => { }); }; - useEffect(() => { - if (!canvasRef.current) { - console.log('Canvas reference is not yet available in the DOM.'); - } - // GetModeler can only be fetched from this hook once since the modeler creates a - // new instance and will attach the same canvasRef container to all instances it fetches. - // Set modelerRef.current to the Context so that it can be used in other components - modelerRef.current = getModeler(canvasRef.current); + const canvasRef = useCallback((div: HTMLDivElement) => { + if (modelerRef.current) return; + + modelerRef.current = BpmnModelerInstance.getInstance(div); initializeEditor().then(() => { // Wait for the initializeEditor to be initialized before attaching event listeners, to avoid trigger add.shape events on first draw @@ -118,10 +108,10 @@ export const useBpmnEditor = (): UseBpmnViewerResult => { useEffect(() => { // Destroy the modeler instance when the component is unmounted return () => { - destroyModeler(); + BpmnModelerInstance.destroyInstance(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - return { canvasRef, modelerRef }; + return canvasRef; }; diff --git a/frontend/packages/process-editor/src/hooks/useBpmnModeler/index.ts b/frontend/packages/process-editor/src/hooks/useBpmnModeler/index.ts deleted file mode 100644 index b1d93287fcf..00000000000 --- a/frontend/packages/process-editor/src/hooks/useBpmnModeler/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useBpmnModeler } from './useBpmnModeler'; diff --git a/frontend/packages/process-editor/src/hooks/useBpmnModeler/useBpmnModeler.test.ts b/frontend/packages/process-editor/src/hooks/useBpmnModeler/useBpmnModeler.test.ts deleted file mode 100644 index fac817172ca..00000000000 --- a/frontend/packages/process-editor/src/hooks/useBpmnModeler/useBpmnModeler.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { useBpmnModeler } from './useBpmnModeler'; -import type Modeler from 'bpmn-js/lib/Modeler'; - -type ModelerMock = Modeler & { container: { container: HTMLDivElement } }; - -jest.mock( - 'bpmn-js/lib/Modeler', - () => - class BpmnModelerMockImpl { - public container: HTMLDivElement; - - constructor(_container: HTMLDivElement) { - this.container = _container; - } - }, -); - -const mockedCanvasHTMLDivElement = `<div>MockedHtml</div>` as unknown as HTMLDivElement; - -describe('useBpmnModeler', () => { - it('should create instance of the BpmnModeler when calling getModeler', () => { - const { result } = renderHook(() => useBpmnModeler()); - const { getModeler } = result.current; - - const modelerInstance = getModeler(mockedCanvasHTMLDivElement) as ModelerMock; - expect(modelerInstance.container.container).toBe(mockedCanvasHTMLDivElement); - }); -}); diff --git a/frontend/packages/process-editor/src/hooks/useBpmnModeler/useBpmnModeler.ts b/frontend/packages/process-editor/src/hooks/useBpmnModeler/useBpmnModeler.ts deleted file mode 100644 index 3a0887f14d9..00000000000 --- a/frontend/packages/process-editor/src/hooks/useBpmnModeler/useBpmnModeler.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type Modeler from 'bpmn-js/lib/Modeler'; -import { BpmnModelerInstance } from '../../utils/bpmnModeler/BpmnModelerInstance'; - -type UseBpmnModelerResult = { - getModeler: (canvasContainer: HTMLDivElement) => Modeler; - destroyModeler: () => void; -}; - -export const useBpmnModeler = (): UseBpmnModelerResult => { - const getModeler = (canvasContainer: HTMLDivElement): Modeler => { - return BpmnModelerInstance.getInstance(canvasContainer); - }; - - const destroyModeler = (): void => { - BpmnModelerInstance.destroyInstance(); - }; - - return { - destroyModeler, - getModeler, - }; -}; diff --git a/frontend/packages/process-editor/src/types/SelectionChangeEvent.ts b/frontend/packages/process-editor/src/types/SelectionChangeEvent.ts index c630ab3f99c..43d72636010 100644 --- a/frontend/packages/process-editor/src/types/SelectionChangeEvent.ts +++ b/frontend/packages/process-editor/src/types/SelectionChangeEvent.ts @@ -2,15 +2,11 @@ import { type BpmnBusinessObjectEditor } from './BpmnBusinessObjectEditor'; export type SelectionChangedEvent = { newSelection: Array<{ - element: { - id: string; - businessObject: BpmnBusinessObjectEditor; - }; + id: string; + businessObject: BpmnBusinessObjectEditor; }>; oldSelection: Array<{ - element: { - id: string; - businessObject: BpmnBusinessObjectEditor; - }; + id: string; + businessObject: BpmnBusinessObjectEditor; }>; }; diff --git a/frontend/packages/process-editor/test/EventListeners.test.ts b/frontend/packages/process-editor/test/EventListeners.test.ts new file mode 100644 index 00000000000..95bc72fe6ba --- /dev/null +++ b/frontend/packages/process-editor/test/EventListeners.test.ts @@ -0,0 +1,210 @@ +import { EventListeners } from './EventListeners'; + +describe('EventListeners', () => { + describe('add', () => { + it('Adds a listener to the given event', () => { + const eventListeners = new EventListeners<{ event: () => void }>(); + const fun = jest.fn(); + const eventName = 'event'; + + eventListeners.add(eventName, fun); + eventListeners.triggerEvent(eventName); + + expect(fun).toHaveBeenCalledTimes(1); + }); + + it('Supports adding multiple listeners to the same event', () => { + const eventListeners = new EventListeners<{ event: () => void }>(); + const fun1 = jest.fn(); + const fun2 = jest.fn(); + const eventName = 'event'; + + eventListeners.add(eventName, fun1); + eventListeners.add(eventName, fun2); + eventListeners.triggerEvent(eventName); + + expect(fun1).toHaveBeenCalledTimes(1); + expect(fun2).toHaveBeenCalledTimes(1); + }); + + it('Supports adding listeners to multiple events', () => { + const eventListeners = new EventListeners<Record<'event1' | 'event2', () => void>>(); + const event1Fun = jest.fn(); + const event2Fun = jest.fn(); + const event1Name = 'event1'; + const event2Name = 'event2'; + + eventListeners.add(event1Name, event1Fun); + eventListeners.add(event2Name, event2Fun); + eventListeners.triggerEvent(event1Name); + eventListeners.triggerEvent(event2Name); + + expect(event1Fun).toHaveBeenCalledTimes(1); + expect(event2Fun).toHaveBeenCalledTimes(1); + }); + + it('Supports adding the same function to multiple events', () => { + const eventListeners = new EventListeners<Record<'event1' | 'event2', () => void>>(); + const fun = jest.fn(); + const event1Name = 'event1'; + const event2Name = 'event2'; + + eventListeners.add(event1Name, fun); + eventListeners.add(event2Name, fun); + eventListeners.triggerEvent(event1Name); + eventListeners.triggerEvent(event2Name); + + expect(fun).toHaveBeenCalledTimes(2); + }); + }); + + describe('remove', () => { + it('Removes the given function from the given event listener', () => { + const eventListeners = new EventListeners<{ event: () => void }>(); + const fun = jest.fn(); + const eventName = 'event'; + eventListeners.add(eventName, fun); + + eventListeners.remove(eventName, fun); + eventListeners.triggerEvent(eventName); + + expect(fun).not.toHaveBeenCalled(); + }); + + it('Does not remove other functions than the given one', () => { + const eventListeners = new EventListeners< + Record<'event.of.interest' | 'another.event', () => void> + >(); + const funToRemove = jest.fn(); + const funOnSameEvent = jest.fn(); + const funOnAnotherEvent = jest.fn(); + const eventOfInterestName = 'event.of.interest'; + const anotherEventName = 'another.event'; + eventListeners.add(eventOfInterestName, funToRemove); + eventListeners.add(eventOfInterestName, funOnSameEvent); + eventListeners.add(anotherEventName, funOnAnotherEvent); + + eventListeners.remove(eventOfInterestName, funToRemove); + eventListeners.triggerEvent(eventOfInterestName); + eventListeners.triggerEvent(anotherEventName); + + expect(funOnSameEvent).toHaveBeenCalled(); + expect(funOnAnotherEvent).toHaveBeenCalled(); + expect(funToRemove).not.toHaveBeenCalled(); + }); + + it('Does only remove the function on the given event listener when the same function also exists on another listener', () => { + const eventListeners = new EventListeners< + Record<'event.of.interest' | 'another.event', () => void> + >(); + const funToRemoveFromSingleEvent = jest.fn(); + const eventOfInterestName = 'event.of.interest'; + const anotherEventName = 'another.event'; + eventListeners.add(eventOfInterestName, funToRemoveFromSingleEvent); + eventListeners.add(anotherEventName, funToRemoveFromSingleEvent); + + eventListeners.remove(eventOfInterestName, funToRemoveFromSingleEvent); + eventListeners.triggerEvent(eventOfInterestName); + eventListeners.triggerEvent(anotherEventName); + + expect(funToRemoveFromSingleEvent).toHaveBeenCalledTimes(1); + }); + + it('Throws the expected error when attempting to remove a function that is not added', () => { + const eventListeners = new EventListeners<{ event: () => void }>(); + const fun = jest.fn(); + const eventName = 'event'; + + expect(() => eventListeners.remove(eventName, fun)).toThrow( + `The provided callback function does not exist on the ${eventName} listener.`, + ); + }); + }); + + describe('triggerEvent', () => { + it('Calls all the functions added to the given event listener with correct parameters', () => { + const eventListeners = new EventListeners<{ event: (p: string) => void }>(); + const fun1 = jest.fn(); + const fun2 = jest.fn(); + const eventName = 'event'; + eventListeners.add(eventName, fun1); + eventListeners.add(eventName, fun2); + const param = 'test'; + + eventListeners.triggerEvent(eventName, param); + + expect(fun1).toHaveBeenCalledTimes(1); + expect(fun1).toHaveBeenCalledWith(param); + expect(fun2).toHaveBeenCalledTimes(1); + expect(fun2).toHaveBeenCalledWith(param); + }); + + it('Supports functions with multiple parameters', () => { + const eventListeners = new EventListeners<{ + event: (p1: string, p2: number, p3: boolean) => void; + }>(); + const fun = jest.fn(); + const eventName = 'event'; + eventListeners.add(eventName, fun); + const param1 = 'test'; + const param2 = 1; + const param3 = true; + + eventListeners.triggerEvent(eventName, param1, param2, param3); + + expect(fun).toHaveBeenCalledTimes(1); + expect(fun).toHaveBeenCalledWith(param1, param2, param3); + }); + + it('Supports functions with no parameters', () => { + const eventListeners = new EventListeners<{ event: () => void }>(); + const fun = jest.fn(); + const eventName = 'event'; + eventListeners.add(eventName, fun); + + eventListeners.triggerEvent(eventName); + + expect(fun).toHaveBeenCalledTimes(1); + expect(fun).toHaveBeenCalledWith(); + }); + + it('Does not call functions on other listeners than the given one', () => { + const eventListeners = new EventListeners< + Record<'event.of.interest' | 'another.event', () => void> + >(); + const funOfInterest = jest.fn(); + const funOnAnotherEvent = jest.fn(); + const eventOfInterestName = 'event.of.interest'; + const anotherEventName = 'another.event'; + eventListeners.add(eventOfInterestName, funOfInterest); + eventListeners.add(anotherEventName, funOnAnotherEvent); + + eventListeners.triggerEvent(eventOfInterestName); + + expect(funOnAnotherEvent).not.toHaveBeenCalled(); + expect(funOfInterest).toHaveBeenCalled(); + }); + }); + + describe('clear', () => { + it('Removes all listeners', () => { + const eventListeners = new EventListeners<Record<'event1' | 'event2', () => void>>(); + const event1Fun1 = jest.fn(); + const event1Fun2 = jest.fn(); + const event2Fun = jest.fn(); + const event1Name = 'event1'; + const event2Name = 'event2'; + eventListeners.add(event1Name, event1Fun1); + eventListeners.add(event1Name, event1Fun2); + eventListeners.add(event2Name, event2Fun); + + eventListeners.clear(); + eventListeners.triggerEvent(event1Name); + eventListeners.triggerEvent(event2Name); + + expect(event1Fun1).not.toHaveBeenCalled(); + expect(event1Fun2).not.toHaveBeenCalled(); + expect(event2Fun).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/frontend/packages/process-editor/test/EventListeners.ts b/frontend/packages/process-editor/test/EventListeners.ts new file mode 100644 index 00000000000..6df2269e375 --- /dev/null +++ b/frontend/packages/process-editor/test/EventListeners.ts @@ -0,0 +1,78 @@ +import { ArrayUtils } from '@studio/pure-functions'; + +type ListenerMap<EventMap extends Record<string, (...args: unknown[]) => void>> = Map< + keyof EventMap, + Array<EventMap[keyof EventMap]> +>; + +export class EventListeners<EventMap extends Record<string, (...args: unknown[]) => void>> { + private readonly list: ListenerMap<EventMap>; + + constructor() { + this.list = new Map(); + } + + triggerEvent<Key extends keyof EventMap>( + eventName: Key, + ...params: Parameters<EventMap[Key]> + ): void { + if (this.has(eventName)) { + const functions = this.get(eventName); + functions.forEach((fun) => fun(...params)); + } + } + + add<Key extends keyof EventMap>(eventName: Key, callback: EventMap[Key]): void { + if (this.has(eventName)) this.addListenerToCurrentList<Key>(eventName, callback); + else this.createNewListenerList<Key>(eventName, [callback]); + } + + private addListenerToCurrentList<Key extends keyof EventMap>( + eventName: Key, + callback: EventMap[Key], + ): void { + const currentListeners = this.get<Key>(eventName); + this.set<Key>(eventName, [...currentListeners, callback]); + } + + private createNewListenerList<Key extends keyof EventMap>( + eventName: Key, + callbacks: EventMap[Key][], + ): void { + this.set<Key>(eventName, callbacks); + } + + remove<Key extends keyof EventMap>(eventName: Key, callback: EventMap[Key]): void { + if (!this.functionExists<Key>(eventName, callback)) + throw new Error( + `The provided callback function does not exist on the ${String(eventName)} listener.`, + ); + + const currentList = this.get<Key>(eventName); + const newList = ArrayUtils.removeItemByValue<EventMap[Key]>(currentList, callback); + this.set<Key>(eventName, newList); + } + + private functionExists<Key extends keyof EventMap>( + eventName: Key, + callback: EventMap[Key], + ): boolean { + return this.has(eventName) && this.get<Key>(eventName).includes(callback); + } + + private has(eventName: keyof EventMap): boolean { + return this.list.has(eventName); + } + + private get<Key extends keyof EventMap>(eventName: Key): EventMap[Key][] | undefined { + return this.list.get(eventName) as EventMap[Key][] | undefined; + } + + private set<Key extends keyof EventMap>(eventName: Key, callbacks: EventMap[Key][]): void { + this.list.set(eventName, callbacks); + } + + clear(): void { + this.list.clear(); + } +} diff --git a/frontend/packages/shared/package.json b/frontend/packages/shared/package.json index 3f6ec03dd60..fbab3dc4a2c 100644 --- a/frontend/packages/shared/package.json +++ b/frontend/packages/shared/package.json @@ -4,15 +4,15 @@ "author": "Altinn", "dependencies": { "classnames": "2.5.1", - "qs": "6.13.1", + "qs": "6.14.0", "react": "18.3.1", "react-dom": "18.3.1", - "react-router-dom": "6.28.1" + "react-router-dom": "6.28.2" }, "devDependencies": { "@types/react": "18.3.18", "jest": "29.7.0", - "typescript": "5.7.2" + "typescript": "5.7.3" }, "license": "3-Clause BSD", "private": true, diff --git a/frontend/packages/shared/src/api/mutations.ts b/frontend/packages/shared/src/api/mutations.ts index d96514f18b4..16c2b3fba8a 100644 --- a/frontend/packages/shared/src/api/mutations.ts +++ b/frontend/packages/shared/src/api/mutations.ts @@ -47,6 +47,7 @@ import { selectedMaskinportenScopesPath, createInstancePath, dataTypePath, + optionListPath, } from 'app-shared/api/paths'; import type { AddLanguagePayload } from 'app-shared/types/api/AddLanguagePayload'; import type { AddRepoParams } from 'app-shared/types/api'; @@ -86,6 +87,7 @@ export const addImage = (org: string, app: string, form: FormData) => post<FormD export const deleteImage = (org: string, app: string, imageName: string) => del(imagePath(org, app, imageName)); export const deleteLayoutSet = (org: string, app: string, layoutSetIdToUpdate: string) => del(layoutSetPath(org, app, layoutSetIdToUpdate)); +export const deleteOptionList = (org: string, app: string, optionListId: string) => del(optionListPath(org, app, optionListId)); export const updateLayoutSetId = (org: string, app: string, layoutSetIdToUpdate: string, newLayoutSetId: string) => put(layoutSetPath(org, app, layoutSetIdToUpdate), newLayoutSetId, { headers: { 'Content-Type': 'application/json' } }); export const addRepo = (repoToAdd: AddRepoParams) => post<Repository>(`${createRepoPath()}${buildQueryParams(repoToAdd)}`); export const addXsdFromRepo = (org: string, app: string, modelPath: string) => post<JsonSchema>(dataModelAddXsdFromRepoPath(org, app, modelPath)); diff --git a/frontend/packages/shared/src/api/paths.js b/frontend/packages/shared/src/api/paths.js index 52bf4a0e5ce..a8ce17184b9 100644 --- a/frontend/packages/shared/src/api/paths.js +++ b/frontend/packages/shared/src/api/paths.js @@ -42,10 +42,10 @@ export const submitFeedbackPath = (org, app) => `${basePath}/${org}/${app}/feedb // FormEditor export const ruleHandlerPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-handler?${s({ layoutSetName })}`; // Get, Post export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-development/widget-settings`; // Get -export const optionListPath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Get +export const optionListPath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Get, Delete export const optionListsPath = (org, app) => `${basePath}/${org}/${app}/options/option-lists`; // Get export const optionListReferencesPath = (org, app) => `${basePath}/${org}/${app}/options/usage`; // Get -export const optionListIdsPath = (org, app) => `${basePath}/${org}/${app}/app-development/option-list-ids`; // Get +export const optionListIdsPath = (org, app) => `${basePath}/${org}/${app}/options`; // Get export const optionListUpdatePath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Put export const optionListIdUpdatePath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/change-name/${optionsListId}`; // Put export const optionListUploadPath = (org, app) => `${basePath}/${org}/${app}/options/upload`; // Post @@ -185,3 +185,6 @@ export const processEditorDataTypePath = (org, app, dataTypeId, taskId) => `${ba // Event Hubs export const SyncEventsWebSocketHub = () => '/sync-hub'; + +// Contact +export const belongsToOrg = () => `${basePath}/contact/belongs-to-org`; diff --git a/frontend/packages/shared/src/api/queries.ts b/frontend/packages/shared/src/api/queries.ts index 342dfc60ed0..2911677abc3 100644 --- a/frontend/packages/shared/src/api/queries.ts +++ b/frontend/packages/shared/src/api/queries.ts @@ -4,6 +4,7 @@ import { appMetadataPath, appPolicyPath, appVersionPath, + belongsToOrg, branchStatusPath, dataModelMetadataPath, dataModelPath, @@ -91,7 +92,9 @@ import type { Policy } from 'app-shared/types/Policy'; import type { RepoDiffResponse } from 'app-shared/types/api/RepoDiffResponse'; import type { ExternalImageUrlValidationResponse } from 'app-shared/types/api/ExternalImageUrlValidationResponse'; import type { MaskinportenScopes } from 'app-shared/types/MaskinportenScope'; -import type { OptionListsReferences, OptionsList, OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; +import type { OptionList } from 'app-shared/types/OptionList'; +import type { OptionListsResponse } from 'app-shared/types/api/OptionListsResponse'; +import type { OptionListReferences } from 'app-shared/types/OptionListReferences'; import type { LayoutSetsModel } from '../types/api/dto/LayoutSetsModel'; import type { AccessPackageResource, PolicyAccessPackageAreaGroup } from 'app-shared/types/PolicyAccessPackages'; import type { DataType } from '../types/DataType'; @@ -120,9 +123,9 @@ export const getImageFileNames = (owner: string, app: string) => get<string[]>(g export const getLayoutNames = (owner: string, app: string) => get<string[]>(layoutNamesPath(owner, app)); export const getLayoutSets = (owner: string, app: string) => get<LayoutSets>(layoutSetsPath(owner, app)); export const getLayoutSetsExtended = (owner: string, app: string) => get<LayoutSetsModel>(layoutSetsPath(owner, app) + '/extended'); -export const getOptionList = (owner: string, app: string, optionsListId: string) => get<OptionsList>(optionListPath(owner, app, optionsListId)); -export const getOptionLists = (owner: string, app: string) => get<OptionsListsResponse>(optionListsPath(owner, app)); -export const getOptionListsReferences = (owner: string, app: string) => get<OptionListsReferences>(optionListReferencesPath(owner, app)); +export const getOptionList = (owner: string, app: string, optionsListId: string) => get<OptionList>(optionListPath(owner, app, optionsListId)); +export const getOptionLists = (owner: string, app: string) => get<OptionListsResponse>(optionListsPath(owner, app)); +export const getOptionListsReferences = (owner: string, app: string) => get<OptionListReferences>(optionListReferencesPath(owner, app)); export const getOptionListIds = (owner: string, app: string) => get<string[]>(optionListIdsPath(owner, app)); export const getOrgList = () => get<OrgList>(orgListUrl()); export const getOrganizations = () => get<Organization[]>(orgsListPath()); @@ -168,3 +171,6 @@ export const getAltinn2DelegationsCount = (org: string, serviceCode: string, ser // ProcessEditor export const getBpmnFile = (org: string, app: string) => get<string>(processEditorPath(org, app)); export const getProcessTaskType = (org: string, app: string, taskId: string) => get<string>(`${processTaskTypePath(org, app, taskId)}`); + +// Contact Page +export const fetchBelongsToGiteaOrg = () => get(belongsToOrg()); diff --git a/frontend/packages/shared/src/getInTouch/providers/PhoneContactProvider.ts b/frontend/packages/shared/src/getInTouch/providers/PhoneContactProvider.ts new file mode 100644 index 00000000000..d1c440c711e --- /dev/null +++ b/frontend/packages/shared/src/getInTouch/providers/PhoneContactProvider.ts @@ -0,0 +1,14 @@ +import { type GetInTouchProvider } from '../interfaces/GetInTouchProvider'; + +type PhoneChannel = 'phone' | 'emergencyPhone'; + +const phoneChannelMap: Record<PhoneChannel, string> = { + phone: 'tel:75006299', + emergencyPhone: 'tel:94490002', +}; + +export class PhoneContactProvider implements GetInTouchProvider<PhoneChannel> { + public buildContactUrl(selectedChannel: PhoneChannel): string { + return phoneChannelMap[selectedChannel]; + } +} diff --git a/frontend/packages/shared/src/hooks/mutations/index.ts b/frontend/packages/shared/src/hooks/mutations/index.ts index d23c7a9b268..06192bfbe8d 100644 --- a/frontend/packages/shared/src/hooks/mutations/index.ts +++ b/frontend/packages/shared/src/hooks/mutations/index.ts @@ -1,4 +1,5 @@ export { useAddOptionListMutation } from './useAddOptionListMutation'; +export { useDeleteOptionListMutation } from './useDeleteOptionListMutation'; export { useUpdateOptionListMutation } from './useUpdateOptionListMutation'; export { useUpdateOptionListIdMutation } from './useUpdateOptionListIdMutation'; export { useUpsertTextResourcesMutation } from './useUpsertTextResourcesMutation'; diff --git a/frontend/packages/shared/src/hooks/mutations/useDeleteOptionListMutation.test.ts b/frontend/packages/shared/src/hooks/mutations/useDeleteOptionListMutation.test.ts new file mode 100644 index 00000000000..3826336dc1b --- /dev/null +++ b/frontend/packages/shared/src/hooks/mutations/useDeleteOptionListMutation.test.ts @@ -0,0 +1,62 @@ +import { app, org } from '@studio/testing/testids'; +import { queriesMock } from '../../mocks/queriesMock'; +import { renderHookWithProviders } from '../../mocks/renderHookWithProviders'; +import { useDeleteOptionListMutation } from './useDeleteOptionListMutation'; +import { createQueryClientMock } from '../../mocks/queryClientMock'; +import { QueryKey } from 'app-shared/types/QueryKey'; + +// Test data: +const optionsListId = 'test'; + +describe('useDeleteOptionListMutation', () => { + test('Calls useDeleteOptionList with correct parameters', async () => { + const renderDeleteOptionListMutationResult = renderHookWithProviders(() => + useDeleteOptionListMutation(org, app), + ).result; + await renderDeleteOptionListMutationResult.current.mutateAsync(optionsListId); + expect(queriesMock.deleteOptionList).toHaveBeenCalledTimes(1); + expect(queriesMock.deleteOptionList).toHaveBeenCalledWith(org, app, optionsListId); + }); + + test('Sets the option list ids query cache without the given option list id', async () => { + const queryClient = createQueryClientMock(); + queryClient.setQueryData([QueryKey.OptionListIds, org, app], [optionsListId]); + const renderDeleteOptionListMutationResult = renderHookWithProviders( + () => useDeleteOptionListMutation(org, app), + { queryClient }, + ).result; + await renderDeleteOptionListMutationResult.current.mutateAsync(optionsListId); + expect(queryClient.getQueryData([QueryKey.OptionListIds, org, app])).toEqual([]); + }); + + test('Invalidates the option lists query cache', async () => { + const queryClient = createQueryClientMock(); + queryClient.setQueryData( + [QueryKey.OptionLists, org, app], + [{ title: optionsListId, data: [] }], + ); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + const renderDeleteOptionListMutationResult = renderHookWithProviders( + () => useDeleteOptionListMutation(org, app), + { queryClient }, + ).result; + await renderDeleteOptionListMutationResult.current.mutateAsync(optionsListId); + expect(invalidateQueriesSpy).toHaveBeenCalledTimes(1); + expect(invalidateQueriesSpy).toHaveBeenCalledWith({ + queryKey: [QueryKey.OptionLists, org, app], + }); + }); + + test('Removes the option list query cache for the given option list id', async () => { + const queryClient = createQueryClientMock(); + queryClient.setQueryData([QueryKey.OptionList, org, app, optionsListId], []); + const renderDeleteOptionListMutationResult = renderHookWithProviders( + () => useDeleteOptionListMutation(org, app), + { queryClient }, + ).result; + await renderDeleteOptionListMutationResult.current.mutateAsync(optionsListId); + expect( + queryClient.getQueryData([QueryKey.OptionList, org, app, optionsListId]), + ).toBeUndefined(); + }); +}); diff --git a/frontend/packages/shared/src/hooks/mutations/useDeleteOptionListMutation.ts b/frontend/packages/shared/src/hooks/mutations/useDeleteOptionListMutation.ts new file mode 100644 index 00000000000..cea4681ce60 --- /dev/null +++ b/frontend/packages/shared/src/hooks/mutations/useDeleteOptionListMutation.ts @@ -0,0 +1,37 @@ +import type { MutationMeta, QueryClient } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useServicesContext } from 'app-shared/contexts/ServicesContext'; +import { QueryKey } from 'app-shared/types/QueryKey'; + +export const useDeleteOptionListMutation = (org: string, app: string, meta?: MutationMeta) => { + const queryClient = useQueryClient(); + const { deleteOptionList } = useServicesContext(); + + return useMutation({ + mutationFn: (optionListId: string) => + deleteOptionList(org, app, optionListId).then(() => optionListId), + onSuccess: (optionListId) => { + setOptionListIdsQueryCache(queryClient, org, app, optionListId); + void queryClient.invalidateQueries({ queryKey: [QueryKey.OptionLists, org, app] }); + void queryClient.removeQueries({ queryKey: [QueryKey.OptionList, org, app, optionListId] }); + }, + meta, + }); +}; + +const setOptionListIdsQueryCache = ( + queryClient: QueryClient, + org: string, + app: string, + optionListId: string, +) => { + const currentOptionListIds = queryClient.getQueryData<string[]>([ + QueryKey.OptionListIds, + org, + app, + ]); + if (currentOptionListIds) { + const updatedOptionListIds = currentOptionListIds.filter((id) => id !== optionListId); + void queryClient.setQueryData([QueryKey.OptionListIds, org, app], updatedOptionListIds); + } +}; diff --git a/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListIdMutation.test.ts b/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListIdMutation.test.ts index 6b2b67a29a5..ce5928c978b 100644 --- a/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListIdMutation.test.ts +++ b/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListIdMutation.test.ts @@ -6,7 +6,7 @@ import { useUpdateOptionListIdMutation } from './useUpdateOptionListIdMutation'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { QueryKey } from 'app-shared/types/QueryKey'; import type { Option } from 'app-shared/types/Option'; -import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; +import type { OptionListsResponse } from 'app-shared/types/api/OptionListsResponse'; // Test data: const optionListId: string = 'optionListId'; @@ -38,7 +38,7 @@ describe('useUpdateOptionListIdMutation', () => { const optionListC = 'optionListC'; const optionListZ = 'optionListZ'; const queryClient = createQueryClientMock(); - const oldData: OptionsListsResponse = [ + const oldData: OptionListsResponse = [ { title: optionListA, data: optionListMock }, { title: optionListB, data: optionListMock }, { title: optionListZ, data: optionListMock }, @@ -52,7 +52,7 @@ describe('useUpdateOptionListIdMutation', () => { optionListId: optionListA, newOptionListId: optionListC, }); - const cacheData: OptionsListsResponse = queryClient.getQueryData([ + const cacheData: OptionListsResponse = queryClient.getQueryData([ QueryKey.OptionLists, org, app, @@ -65,7 +65,7 @@ describe('useUpdateOptionListIdMutation', () => { test('Invalidates the optionListIds query cache', async () => { const queryClient = createQueryClientMock(); const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); - const oldData: OptionsListsResponse = [ + const oldData: OptionListsResponse = [ { title: 'firstOptionList', data: optionListMock }, { title: 'optionListId', data: optionListMock }, { title: 'lastOptionList', data: optionListMock }, diff --git a/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListIdMutation.ts b/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListIdMutation.ts index 253cc4f7233..3eac0807617 100644 --- a/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListIdMutation.ts +++ b/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListIdMutation.ts @@ -1,5 +1,5 @@ import { QueryKey } from 'app-shared/types/QueryKey'; -import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; +import type { OptionListsResponse } from 'app-shared/types/api/OptionListsResponse'; import { useQueryClient, useMutation } from '@tanstack/react-query'; import { useServicesContext } from 'app-shared/contexts/ServicesContext'; import { ArrayUtils } from '@studio/pure-functions'; @@ -21,7 +21,7 @@ export const useUpdateOptionListIdMutation = (org: string, app: string) => { })); }, onSuccess: ({ optionListId, newOptionListId }) => { - const oldData: OptionsListsResponse = queryClient.getQueryData([ + const oldData: OptionListsResponse = queryClient.getQueryData([ QueryKey.OptionLists, org, app, @@ -38,10 +38,10 @@ export const useUpdateOptionListIdMutation = (org: string, app: string) => { const changeIdAndSortCacheData = ( oldId: string, newId: string, - oldData: OptionsListsResponse, -): OptionsListsResponse => { + oldData: OptionListsResponse, +): OptionListsResponse => { const oldOptionList = oldData.find((optionList) => optionList.title === oldId); - const newOptionLists: OptionsListsResponse = ArrayUtils.replaceByPredicate( + const newOptionLists: OptionListsResponse = ArrayUtils.replaceByPredicate( oldData, (optionList) => optionList.title === oldId, { ...oldOptionList, title: newId }, diff --git a/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListMutation.test.ts b/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListMutation.test.ts index 7b1d40658d8..db457d48f77 100644 --- a/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListMutation.test.ts +++ b/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListMutation.test.ts @@ -10,11 +10,11 @@ import { createQueryClientMock } from '../../mocks/queryClientMock'; import { QueryKey } from 'app-shared/types/QueryKey'; // Test data: -const optionsListId = 'test'; +const optionListId = 'test'; const option1: Option = { value: 'test', label: 'test' }; -const optionsList: Option[] = [option1]; +const optionList: Option[] = [option1]; const updatedOptionsList: Option[] = [{ ...option1, description: 'description' }]; -const args: UpdateOptionListMutationArgs = { optionListId: optionsListId, optionsList }; +const args: UpdateOptionListMutationArgs = { optionListId, optionList }; const updateOptionList = jest.fn().mockImplementation(() => Promise.resolve(updatedOptionsList)); describe('useUpdateOptionListMutation', () => { @@ -24,14 +24,14 @@ describe('useUpdateOptionListMutation', () => { ).result; await renderUpdateOptionsListMutationResult.current.mutateAsync(args); expect(queriesMock.updateOptionList).toHaveBeenCalledTimes(1); - expect(queriesMock.updateOptionList).toHaveBeenCalledWith(org, app, optionsListId, optionsList); + expect(queriesMock.updateOptionList).toHaveBeenCalledWith(org, app, optionListId, optionList); }); - test('Sets the updated options list on the cache for all options lists when cache contains the list', async () => { + test('Sets the updated option list on the cache for all options lists when cache contains the list', async () => { const queryClient = createQueryClientMock(); queryClient.setQueryData( [QueryKey.OptionLists, org, app], - [{ title: optionsListId, data: optionsList }], + [{ title: optionListId, data: optionList }], ); const renderUpdateOptionsListMutationResult = renderHookWithProviders( () => useUpdateOptionListMutation(org, app), @@ -40,15 +40,15 @@ describe('useUpdateOptionListMutation', () => { await renderUpdateOptionsListMutationResult.current.mutateAsync(args); expect(queryClient.getQueryData([QueryKey.OptionLists, org, app])).toEqual([ { - title: optionsListId, + title: optionListId, data: updatedOptionsList, }, ]); }); - test('Adds the new options list on the cache for all options lists when cache does not contain the list', async () => { + test('Adds the new option list on the cache for all options lists when cache does not contain the list', async () => { const queryClient = createQueryClientMock(); - const existingOptionsList = { title: 'some-other-options-list-id', data: optionsList }; + const existingOptionsList = { title: 'some-other-options-list-id', data: optionList }; queryClient.setQueryData([QueryKey.OptionLists, org, app], [existingOptionsList]); const renderUpdateOptionsListMutationResult = renderHookWithProviders( () => useUpdateOptionListMutation(org, app), @@ -57,7 +57,7 @@ describe('useUpdateOptionListMutation', () => { await renderUpdateOptionsListMutationResult.current.mutateAsync(args); expect(queryClient.getQueryData([QueryKey.OptionLists, org, app])).toEqual([ { - title: optionsListId, + title: optionListId, data: updatedOptionsList, }, existingOptionsList, @@ -71,7 +71,7 @@ describe('useUpdateOptionListMutation', () => { { queries: { updateOptionList }, queryClient }, ).result; await renderUpdateOptionsListMutationResult.current.mutateAsync(args); - expect(queryClient.getQueryData([QueryKey.OptionList, org, app, optionsListId])).toEqual( + expect(queryClient.getQueryData([QueryKey.OptionList, org, app, optionListId])).toEqual( updatedOptionsList, ); }); diff --git a/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListMutation.ts b/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListMutation.ts index fd4e15c4e23..18d43277706 100644 --- a/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListMutation.ts +++ b/frontend/packages/shared/src/hooks/mutations/useUpdateOptionListMutation.ts @@ -1,36 +1,32 @@ import type { MutationMeta } from '@tanstack/react-query'; import { QueryKey } from 'app-shared/types/QueryKey'; -import type { Option } from 'app-shared/types/Option'; -import type { - OptionsList, - OptionsListData, - OptionsListsResponse, -} from 'app-shared/types/api/OptionsLists'; +import type { OptionList, OptionListData } from 'app-shared/types/OptionList'; +import type { OptionListsResponse } from 'app-shared/types/api/OptionListsResponse'; import { useQueryClient, useMutation } from '@tanstack/react-query'; import { useServicesContext } from 'app-shared/contexts/ServicesContext'; import { ArrayUtils } from '@studio/pure-functions'; export interface UpdateOptionListMutationArgs { optionListId: string; - optionsList: Option[]; + optionList: OptionList; } export const useUpdateOptionListMutation = (org: string, app: string, meta?: MutationMeta) => { const queryClient = useQueryClient(); const { updateOptionList } = useServicesContext(); - return useMutation<Option[], Error, UpdateOptionListMutationArgs>({ - mutationFn: ({ optionListId, optionsList }: UpdateOptionListMutationArgs) => { - return updateOptionList(org, app, optionListId, optionsList); + return useMutation<OptionList, Error, UpdateOptionListMutationArgs>({ + mutationFn: ({ optionListId, optionList }: UpdateOptionListMutationArgs) => { + return updateOptionList(org, app, optionListId, optionList); }, - onSuccess: (updatedOptionsList: Option[], { optionListId }) => { - const oldData: OptionsListsResponse = queryClient.getQueryData([ + onSuccess: (updatedOptionsList: OptionList, { optionListId }) => { + const oldData: OptionListsResponse = queryClient.getQueryData([ QueryKey.OptionLists, org, app, ]); if (isOptionsListInOptionListsCache(oldData)) { - const newData = updateListInOptionsListsData(optionListId, updatedOptionsList, oldData); + const newData = updateListInOptionListDataList(optionListId, updatedOptionsList, oldData); queryClient.setQueryData([QueryKey.OptionLists, org, app], newData); } queryClient.setQueryData([QueryKey.OptionList, org, app, optionListId], updatedOptionsList); @@ -40,50 +36,50 @@ export const useUpdateOptionListMutation = (org: string, app: string, meta?: Mut }); }; -const isOptionsListInOptionListsCache = (data: OptionsListsResponse | null): boolean => !!data; +const isOptionsListInOptionListsCache = (data: OptionListsResponse | null): boolean => !!data; -const updateListInOptionsListsData = ( - optionsListId: string, - updatedOptionsList: OptionsList, - oldData: OptionsListsResponse, -): OptionsListsResponse => { - const [oldOptionsListData, optionsListExists]: [OptionsListData | undefined, boolean] = - getOldOptionsListData(oldData, optionsListId); +const updateListInOptionListDataList = ( + optionListId: string, + updatedOptionList: OptionList, + oldData: OptionListsResponse, +): OptionListsResponse => { + const [oldOptionsListData, optionsListExists]: [OptionListData | undefined, boolean] = + getOldOptionListData(oldData, optionListId); if (optionsListExists) { - return updateExistingOptionsList(oldData, oldOptionsListData, updatedOptionsList); + return updateExistingOptionList(oldData, oldOptionsListData, updatedOptionList); } - return addNewOptionsList(oldData, optionsListId, updatedOptionsList); + return addNewOptionList(oldData, optionListId, updatedOptionList); }; -const getOldOptionsListData = ( - oldData: OptionsListsResponse, - optionsListId: string, -): [OptionsListData | undefined, boolean] => { +const getOldOptionListData = ( + oldData: OptionListsResponse, + optionListId: string, +): [OptionListData | undefined, boolean] => { const oldOptionsListData = oldData.find( - (optionsListData) => optionsListData.title === optionsListId, + (optionListData) => optionListData.title === optionListId, ); return [oldOptionsListData, !!oldOptionsListData]; }; -const updateExistingOptionsList = ( - oldData: OptionsListsResponse, - oldOptionsListData: OptionsListData, - newOptionsList: OptionsList, +const updateExistingOptionList = ( + oldData: OptionListsResponse, + oldOptionListData: OptionListData, + newOptionList: OptionList, ) => { return ArrayUtils.replaceByPredicate( oldData, - (optionsList) => optionsList.title === oldOptionsListData.title, + (optionsList) => optionsList.title === oldOptionListData.title, { - ...oldOptionsListData, - data: newOptionsList, + ...oldOptionListData, + data: newOptionList, }, ); }; -const addNewOptionsList = ( - oldData: OptionsListsResponse, - optionsListTitle: string, - newOptionsList: OptionsList, +const addNewOptionList = ( + oldData: OptionListsResponse, + optionListTitle: string, + newOptionList: OptionList, ) => { - return ArrayUtils.prepend(oldData, { title: optionsListTitle, data: newOptionsList }); + return ArrayUtils.prepend(oldData, { title: optionListTitle, data: newOptionList }); }; diff --git a/frontend/packages/shared/src/hooks/queries/useOptionListQuery.test.ts b/frontend/packages/shared/src/hooks/queries/useOptionListQuery.test.ts index 2876ebefa9f..2bb5300ec69 100644 --- a/frontend/packages/shared/src/hooks/queries/useOptionListQuery.test.ts +++ b/frontend/packages/shared/src/hooks/queries/useOptionListQuery.test.ts @@ -4,7 +4,7 @@ import { renderHookWithProviders } from 'app-shared/mocks/renderHookWithProvider import { useOptionListQuery } from 'app-shared/hooks/queries/useOptionListQuery'; import { waitFor } from '@testing-library/react'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; -import type { OptionsList } from 'app-shared/types/api/OptionsLists'; +import type { OptionList } from 'app-shared/types/OptionList'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; const optionsListId = 'optionsListId'; @@ -16,7 +16,7 @@ describe('useOptionListQuery', () => { }); it('getOptionList returns optionList as is', async () => { - const optionsList: OptionsList = [{ value: 'value', label: 'label' }]; + const optionsList: OptionList = [{ value: 'value', label: 'label' }]; const getOptionList = jest.fn().mockImplementation(() => Promise.resolve(optionsList)); const { current: currentResult } = await render({ getOptionList }); expect(currentResult.data).toBe(optionsList); diff --git a/frontend/packages/shared/src/hooks/queries/useOptionListQuery.ts b/frontend/packages/shared/src/hooks/queries/useOptionListQuery.ts index 91ec12df941..fa0f44b5159 100644 --- a/frontend/packages/shared/src/hooks/queries/useOptionListQuery.ts +++ b/frontend/packages/shared/src/hooks/queries/useOptionListQuery.ts @@ -2,15 +2,15 @@ import { useServicesContext } from 'app-shared/contexts/ServicesContext'; import { QueryKey } from 'app-shared/types/QueryKey'; import type { UseQueryResult } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; -import type { OptionsList } from 'app-shared/types/api/OptionsLists'; +import type { OptionList } from 'app-shared/types/OptionList'; export const useOptionListQuery = ( org: string, app: string, optionListId: string, -): UseQueryResult<OptionsList> => { +): UseQueryResult<OptionList> => { const { getOptionList } = useServicesContext(); - return useQuery<OptionsList>({ + return useQuery<OptionList>({ queryKey: [QueryKey.OptionList, org, app, optionListId], queryFn: () => getOptionList(org, app, optionListId), }); diff --git a/frontend/packages/shared/src/hooks/queries/useOptionListsQuery.ts b/frontend/packages/shared/src/hooks/queries/useOptionListsQuery.ts index 9123f432848..7ea17f6f4a7 100644 --- a/frontend/packages/shared/src/hooks/queries/useOptionListsQuery.ts +++ b/frontend/packages/shared/src/hooks/queries/useOptionListsQuery.ts @@ -2,14 +2,14 @@ import { useServicesContext } from 'app-shared/contexts/ServicesContext'; import { QueryKey } from 'app-shared/types/QueryKey'; import type { UseQueryResult } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; -import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; +import type { OptionListsResponse } from 'app-shared/types/api/OptionListsResponse'; export const useOptionListsQuery = ( org: string, app: string, -): UseQueryResult<OptionsListsResponse> => { +): UseQueryResult<OptionListsResponse> => { const { getOptionLists } = useServicesContext(); - return useQuery<OptionsListsResponse>({ + return useQuery<OptionListsResponse>({ queryKey: [QueryKey.OptionLists, org, app], queryFn: () => getOptionLists(org, app), }); diff --git a/frontend/packages/shared/src/hooks/queries/useOptionListsReferencesQuery.ts b/frontend/packages/shared/src/hooks/queries/useOptionListsReferencesQuery.ts index c8c7415580f..e1449161bbe 100644 --- a/frontend/packages/shared/src/hooks/queries/useOptionListsReferencesQuery.ts +++ b/frontend/packages/shared/src/hooks/queries/useOptionListsReferencesQuery.ts @@ -2,14 +2,14 @@ import { useServicesContext } from 'app-shared/contexts/ServicesContext'; import { QueryKey } from 'app-shared/types/QueryKey'; import type { UseQueryResult } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; -import type { OptionListsReferences } from 'app-shared/types/api/OptionsLists'; +import type { OptionListReferences } from 'app-shared/types/OptionListReferences'; export const useOptionListsReferencesQuery = ( org: string, app: string, -): UseQueryResult<OptionListsReferences> => { +): UseQueryResult<OptionListReferences> => { const { getOptionListsReferences } = useServicesContext(); - return useQuery<OptionListsReferences>({ + return useQuery<OptionListReferences>({ queryKey: [QueryKey.OptionListsUsage, org, app], queryFn: () => getOptionListsReferences(org, app), }); diff --git a/frontend/packages/shared/src/mocks/queriesMock.ts b/frontend/packages/shared/src/mocks/queriesMock.ts index 8a31eadb0ea..7ce812a6228 100644 --- a/frontend/packages/shared/src/mocks/queriesMock.ts +++ b/frontend/packages/shared/src/mocks/queriesMock.ts @@ -69,13 +69,11 @@ import type { DeploymentsResponse } from 'app-shared/types/api/DeploymentsRespon import type { RepoDiffResponse } from 'app-shared/types/api/RepoDiffResponse'; import type { ExternalImageUrlValidationResponse } from 'app-shared/types/api/ExternalImageUrlValidationResponse'; import type { MaskinportenScope } from 'app-shared/types/MaskinportenScope'; -import type { - OptionListsReferences, - OptionsList, - OptionsListsResponse, -} from 'app-shared/types/api/OptionsLists'; +import type { OptionList } from 'app-shared/types/OptionList'; +import type { OptionListReferences } from 'app-shared/types/OptionListReferences'; import type { LayoutSetsModel } from '../types/api/dto/LayoutSetsModel'; import { layoutSetsExtendedMock } from '@altinn/ux-editor/testing/layoutSetsMock'; +import type { OptionListsResponse } from 'app-shared/types/api/OptionListsResponse'; export const queriesMock: ServicesContextProps = { // Queries @@ -113,11 +111,11 @@ export const queriesMock: ServicesContextProps = { .fn() .mockImplementation(() => Promise.resolve<LayoutSetsModel>(layoutSetsExtendedMock)), getOptionListIds: jest.fn().mockImplementation(() => Promise.resolve<string[]>([])), - getOptionList: jest.fn().mockImplementation(() => Promise.resolve<OptionsList>([])), - getOptionLists: jest.fn().mockImplementation(() => Promise.resolve<OptionsListsResponse>([])), + getOptionList: jest.fn().mockImplementation(() => Promise.resolve<OptionList>([])), + getOptionLists: jest.fn().mockImplementation(() => Promise.resolve<OptionListsResponse>([])), getOptionListsReferences: jest .fn() - .mockImplementation(() => Promise.resolve<OptionListsReferences>([])), + .mockImplementation(() => Promise.resolve<OptionListReferences>([])), getOrgList: jest.fn().mockImplementation(() => Promise.resolve<OrgList>(orgList)), getOrganizations: jest.fn().mockImplementation(() => Promise.resolve<Organization[]>([])), getRepoMetadata: jest.fn().mockImplementation(() => Promise.resolve<Repository>(repository)), @@ -194,6 +192,11 @@ export const queriesMock: ServicesContextProps = { .mockImplementation(() => Promise.resolve<MaskinportenScope[]>([])), updateSelectedMaskinportenScopes: jest.fn().mockImplementation(() => Promise.resolve()), + // Queries - Contact + fetchBelongsToGiteaOrg: jest + .fn() + .mockImplementation(() => Promise.resolve({ belongsToOrg: true })), + // Mutations addAppAttachmentMetadata: jest.fn().mockImplementation(() => Promise.resolve()), addDataTypeToAppMetadata: jest.fn().mockImplementation(() => Promise.resolve()), @@ -220,6 +223,7 @@ export const queriesMock: ServicesContextProps = { deleteImage: jest.fn().mockImplementation(() => Promise.resolve()), deleteLanguageCode: jest.fn().mockImplementation(() => Promise.resolve()), deleteLayoutSet: jest.fn().mockImplementation(() => Promise.resolve()), + deleteOptionList: jest.fn().mockImplementation(() => Promise.resolve()), generateModels: jest.fn().mockImplementation(() => Promise.resolve()), logout: jest.fn().mockImplementation(() => Promise.resolve()), pushRepoChanges: jest.fn().mockImplementation(() => Promise.resolve()), diff --git a/frontend/packages/shared/src/types/ComponentSpecificConfig.ts b/frontend/packages/shared/src/types/ComponentSpecificConfig.ts index 9dbccf45a19..db8474f8145 100644 --- a/frontend/packages/shared/src/types/ComponentSpecificConfig.ts +++ b/frontend/packages/shared/src/types/ComponentSpecificConfig.ts @@ -7,6 +7,7 @@ import type { ActionButtonAction } from 'app-shared/types/ActionButtonAction'; import type { GridRow } from 'app-shared/types/GridRow'; import type { HTMLAutoCompleteValue } from 'app-shared/types/HTMLAutoCompleteValue'; import type { BooleanExpression, StringExpression } from '@studio/components'; +import type { InternalBindingFormat } from '@altinn/ux-editor/utils/dataModelUtils'; type DataModelBindingsForAddress = { address: string; @@ -38,12 +39,12 @@ type DataModelBindingsList = { }; type DataModelBindingsOptionsSimple = { - simpleBinding: string; + simpleBinding: string | InternalBindingFormat; metadata?: string; }; export type DataModelBindingsSimple = { - simpleBinding: string; + simpleBinding: string | InternalBindingFormat; }; type DataModelBindingsForFileUpload = DataModelBindingsSimple | DataModelBindingsList; diff --git a/frontend/packages/shared/src/types/OptionList.ts b/frontend/packages/shared/src/types/OptionList.ts new file mode 100644 index 00000000000..b35965201c8 --- /dev/null +++ b/frontend/packages/shared/src/types/OptionList.ts @@ -0,0 +1,9 @@ +import type { Option } from 'app-shared/types/Option'; + +export type OptionList = Option[]; + +export type OptionListData = { + title: string; + data?: OptionList; + hasError?: boolean; +}; diff --git a/frontend/packages/shared/src/types/OptionListReferences.ts b/frontend/packages/shared/src/types/OptionListReferences.ts new file mode 100644 index 00000000000..2a1bbba083d --- /dev/null +++ b/frontend/packages/shared/src/types/OptionListReferences.ts @@ -0,0 +1,8 @@ +import type { CodeListIdSource } from '@studio/content-library'; + +export type OptionListReferences = OptionListReference[]; + +export type OptionListReference = { + optionListId: string; + optionListIdSources: CodeListIdSource[]; +}; diff --git a/frontend/packages/shared/src/types/QueryKey.ts b/frontend/packages/shared/src/types/QueryKey.ts index ec1fa3aa2a9..c5562fc0bc5 100644 --- a/frontend/packages/shared/src/types/QueryKey.ts +++ b/frontend/packages/shared/src/types/QueryKey.ts @@ -5,6 +5,7 @@ export enum QueryKey { AppPolicy = 'AppPolicy', AppReleases = 'AppReleases', AppVersion = 'AppVersion', + BelongsToOrg = 'BelongsToOrg', BranchStatus = 'BranchStatus', CurrentUser = 'CurrentUser', DataModelMetadata = 'DataModelMetadata', @@ -14,7 +15,6 @@ export enum QueryKey { DeployPermissions = 'DeployPermissions', Environments = 'Environments', FetchBpmn = 'FetchBpmn', - FetchTextResources = 'FetchTextResources', FormComponent = 'FormComponent', FormLayoutSettings = 'FormLayoutSettings', FormLayouts = 'FormLayouts', @@ -24,7 +24,6 @@ export enum QueryKey { InstanceId = 'InstanceId', JsonSchema = 'JsonSchema', LayoutNames = 'LayoutNames', - LayoutSchema = 'LayoutSchema', LayoutSets = 'LayoutSets', LayoutSetsExtended = 'LayoutSetsExtended', OptionList = 'OptionList', @@ -36,7 +35,6 @@ export enum QueryKey { ProcessTaskDataType = 'ProcessTaskDataType', RepoMetadata = 'RepoMetadata', RepoPullData = 'RepoPullData', - RepoReset = 'RepoReset', RepoStatus = 'RepoStatus', RepoDiff = 'RepoDiff', RuleConfig = 'RuleConfig', @@ -60,9 +58,6 @@ export enum QueryKey { ResourcePolicyAccessPackages = 'ResourcePolicyAccessPackages', ResourcePolicyAccessPackageServices = 'ResourcePolicyAccessPackageServices', ResourcePublishStatus = 'ResourcePublishStatus', - ResourceSectors = 'ResourceSectors', - ResourceThematicEurovoc = 'ResourceThematicEurovoc', - ResourceThematicLos = 'ResourceThematicLos', SingleResource = 'SingleResource', ValidatePolicy = 'ValidatePolicy', ValidateResource = 'ValidateResource', diff --git a/frontend/packages/shared/src/types/api/OptionListsResponse.ts b/frontend/packages/shared/src/types/api/OptionListsResponse.ts new file mode 100644 index 00000000000..30e0275146d --- /dev/null +++ b/frontend/packages/shared/src/types/api/OptionListsResponse.ts @@ -0,0 +1,3 @@ +import type { OptionListData } from '../OptionList'; + +export type OptionListsResponse = OptionListData[]; diff --git a/frontend/packages/shared/src/types/api/OptionsLists.ts b/frontend/packages/shared/src/types/api/OptionsLists.ts deleted file mode 100644 index 27c46294c52..00000000000 --- a/frontend/packages/shared/src/types/api/OptionsLists.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Option } from 'app-shared/types/Option'; -import type { CodeListIdSource } from '@studio/content-library'; - -export type OptionsList = Option[]; - -export type OptionsListData = { - title: string; - data?: OptionsList; - hasError?: boolean; -}; - -export type OptionsListsResponse = OptionsListData[]; - -export type OptionListsReference = { - optionListId: string; - optionListIdSources: CodeListIdSource[]; -}; - -export type OptionListsReferences = OptionListsReference[]; diff --git a/frontend/packages/shared/src/utils/featureToggleUtils.ts b/frontend/packages/shared/src/utils/featureToggleUtils.ts index df5dfc0aa20..b33368c4797 100644 --- a/frontend/packages/shared/src/utils/featureToggleUtils.ts +++ b/frontend/packages/shared/src/utils/featureToggleUtils.ts @@ -8,9 +8,7 @@ export enum FeatureFlag { ComponentConfigBeta = 'componentConfigBeta', ExportForm = 'exportForm', Maskinporten = 'maskinporten', - MultipleDataModelsPerTask = 'multipleDataModelsPerTask', OptionListEditor = 'optionListEditor', - AccessPackages = 'accessPackages', ShouldOverrideAppLibCheck = 'shouldOverrideAppLibCheck', Subform = 'subform', Summary2 = 'summary2', diff --git a/frontend/packages/shared/src/utils/layoutSetsUtils.ts b/frontend/packages/shared/src/utils/layoutSetsUtils.ts index a774fc7a3b8..9f69549b389 100644 --- a/frontend/packages/shared/src/utils/layoutSetsUtils.ts +++ b/frontend/packages/shared/src/utils/layoutSetsUtils.ts @@ -18,8 +18,7 @@ export const getLayoutSetIdValidationErrorKey = ( if (!newLayoutSetId || newLayoutSetId.trim() === '') return 'validation_errors.required'; if (newLayoutSetId.length === 1) return 'process_editor.configuration_panel_custom_receipt_layout_set_name_validation'; - if (!validateLayoutNameAndLayoutSetName(newLayoutSetId)) - return 'validation_errors.file_name_invalid'; + if (!validateLayoutNameAndLayoutSetName(newLayoutSetId)) return 'validation_errors.name_invalid'; if (layoutSets.sets.some((set) => StringUtils.areCaseInsensitiveEqual(set.id, newLayoutSetId))) return 'process_editor.configuration_panel_layout_set_id_not_unique'; return null; diff --git a/frontend/packages/text-editor/package.json b/frontend/packages/text-editor/package.json index 0fd32f16ff8..52140edc7ed 100644 --- a/frontend/packages/text-editor/package.json +++ b/frontend/packages/text-editor/package.json @@ -2,7 +2,7 @@ "name": "@altinn/text-editor", "version": "0.1.0", "dependencies": { - "iso-639-1": "3.1.3" + "iso-639-1": "3.1.4" }, "devDependencies": { "jest": "29.7.0" @@ -12,7 +12,7 @@ "peerDependencies": { "react": "18.3.1", "react-dom": "18.3.1", - "typescript": "5.7.2" + "typescript": "5.7.3" }, "scripts": { "test": "jest" diff --git a/frontend/packages/text-editor/src/TextEditor.module.css b/frontend/packages/text-editor/src/TextEditor.module.css index d9f80010871..633a8bd8976 100644 --- a/frontend/packages/text-editor/src/TextEditor.module.css +++ b/frontend/packages/text-editor/src/TextEditor.module.css @@ -18,14 +18,14 @@ border-bottom: 1px solid #bcc7cc; padding: var(--fds-spacing-6); display: flex; - align-items: center; + align-items: flex-end; justify-content: space-between; } .filterAndSearch { display: flex; flex-direction: row; - align-items: center; + align-items: flex-end; gap: var(--fds-spacing-4); } diff --git a/frontend/packages/ux-editor-v3/package.json b/frontend/packages/ux-editor-v3/package.json index dfd3d855570..d0c6546acd9 100644 --- a/frontend/packages/ux-editor-v3/package.json +++ b/frontend/packages/ux-editor-v3/package.json @@ -11,7 +11,7 @@ "react-dom": "18.3.1", "react-redux": "9.2.0", "redux": "5.0.1", - "typescript": "5.7.2", + "typescript": "5.7.3", "uuid": "10.0.0" }, "devDependencies": { diff --git a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditCodeList.tsx b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditCodeList.tsx index 648115ab9cf..348f18f281e 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditCodeList.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditCodeList.tsx @@ -82,7 +82,7 @@ export function EditCodeList({ component, handleComponentChange }: IGenericEditC <p style={{ marginBottom: 0 }}> <Trans i18nKey={'ux_editor.modal_properties_code_list_read_more'}> <a - href={altinnDocsUrl({ relativeUrl: 'altinn-studio/reference/data/options/' })} + href={altinnDocsUrl({ relativeUrl: 'altinn-studio/guides/development/options/' })} target='_newTab' rel='noopener noreferrer' /> diff --git a/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.test.tsx b/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.test.tsx index c050f04e9c6..78a6e62d258 100644 --- a/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.test.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/DesignView/PageAccordion/NavigationMenu/InputPopover/InputPopover.test.tsx @@ -155,13 +155,13 @@ describe('InputPopover', () => { const input = screen.getByLabelText(textMock('ux_editor.input_popover_label')); expect(input).toHaveValue(mockOldName); - const errorMessage = screen.queryByText(textMock('ux_editor.pages_error_length')); + const errorMessage = screen.queryByText(textMock('validation_errors.name_invalid')); expect(errorMessage).not.toBeInTheDocument(); const longWord = '123456789012345678901234567890'; await user.type(input, longWord); - const errorMessageAfter = screen.getByText(textMock('ux_editor.pages_error_length')); + const errorMessageAfter = screen.getByText(textMock('validation_errors.name_invalid')); expect(errorMessageAfter).toBeInTheDocument(); }); @@ -173,13 +173,13 @@ describe('InputPopover', () => { const input = screen.getByLabelText(textMock('ux_editor.input_popover_label')); expect(input).toHaveValue(mockOldName); - const errorMessage = screen.queryByText(textMock('ux_editor.pages_error_format')); + const errorMessage = screen.queryByText(textMock('validation_errors.name_invalid')); expect(errorMessage).not.toBeInTheDocument(); const illegalWord = ',,,'; await user.type(input, illegalWord); - const errorMessageAfter = screen.getByText(textMock('ux_editor.pages_error_format')); + const errorMessageAfter = screen.getByText(textMock('validation_errors.name_invalid')); expect(errorMessageAfter).toBeInTheDocument(); }); diff --git a/frontend/packages/ux-editor-v3/src/utils/designViewUtils/designViewUtils.test.ts b/frontend/packages/ux-editor-v3/src/utils/designViewUtils/designViewUtils.test.ts index 829ec4dedae..fa41e514c59 100644 --- a/frontend/packages/ux-editor-v3/src/utils/designViewUtils/designViewUtils.test.ts +++ b/frontend/packages/ux-editor-v3/src/utils/designViewUtils/designViewUtils.test.ts @@ -4,7 +4,7 @@ const mockNewNameCandidateCorrect: string = 'newPage'; const mockNewNameCandidateExists: string = 'page2'; const mockNewNameCandidateEmpty: string = ''; const mockNewNameCandidateTooLong: string = 'ThisStringIsTooooooooooooooLong'; -const mockNewNameCandidateIllegal: string = 'Page????'; +const mockNewNameCandidateInvalid: string = 'Page????'; const mockOldName: string = 'oldName'; const mockLayoutOrder: string[] = [mockOldName, mockNewNameCandidateExists, 'page3']; @@ -41,22 +41,22 @@ describe('designViewUtils', () => { expect(nameErrorkey).toEqual('ux_editor.pages_error_empty'); }); - it('returns length error key when name is too long', () => { + it('returns name invalid error key when name is too long', () => { const nameErrorkey = getPageNameErrorKey( mockNewNameCandidateTooLong, mockOldName, mockLayoutOrder, ); - expect(nameErrorkey).toEqual('ux_editor.pages_error_length'); + expect(nameErrorkey).toEqual('validation_errors.name_invalid'); }); - it('returns format error key when name contains illegal characters', () => { + it('returns name invalid error key when name contains invalid characters', () => { const nameErrorkey = getPageNameErrorKey( - mockNewNameCandidateIllegal, + mockNewNameCandidateInvalid, mockOldName, mockLayoutOrder, ); - expect(nameErrorkey).toEqual('ux_editor.pages_error_format'); + expect(nameErrorkey).toEqual('validation_errors.name_invalid'); }); it('returns null when oldname and new name is the same', () => { diff --git a/frontend/packages/ux-editor-v3/src/utils/designViewUtils/designViewUtils.ts b/frontend/packages/ux-editor-v3/src/utils/designViewUtils/designViewUtils.ts index 545efe32a9f..478a23a988b 100644 --- a/frontend/packages/ux-editor-v3/src/utils/designViewUtils/designViewUtils.ts +++ b/frontend/packages/ux-editor-v3/src/utils/designViewUtils/designViewUtils.ts @@ -25,10 +25,8 @@ export const getPageNameErrorKey = ( return 'ux_editor.pages_error_unique'; } else if (!newNameCandidate) { return 'ux_editor.pages_error_empty'; - } else if (newNameCandidate.length > 30) { - return 'ux_editor.pages_error_length'; } else if (!validateLayoutNameAndLayoutSetName(newNameCandidate)) { - return 'ux_editor.pages_error_format'; + return 'validation_errors.name_invalid'; } else { return null; } diff --git a/frontend/packages/ux-editor/package.json b/frontend/packages/ux-editor/package.json index 94967ac5441..29597afe24e 100644 --- a/frontend/packages/ux-editor/package.json +++ b/frontend/packages/ux-editor/package.json @@ -8,7 +8,7 @@ "classnames": "2.5.1", "react": "18.3.1", "react-dom": "18.3.1", - "typescript": "5.7.2", + "typescript": "5.7.3", "uuid": "10.0.0" }, "devDependencies": { diff --git a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.test.tsx b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.test.tsx index bc76a9faeb4..94831c0d60c 100644 --- a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.test.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.test.tsx @@ -98,6 +98,12 @@ describe('LayoutSetsContainer', () => { }); expect(deleteSubformButton).not.toBeInTheDocument(); }); + + it('should render an error message if selectedFormLayoutSetName is not in layoutSets', async () => { + render({ layoutSets: { sets: [] }, selectedLayoutSet: 'non-existing-layout-set' }); + const errorMessage = screen.getByText(textMock('general.fetch_error_message')); + expect(errorMessage).toBeInTheDocument(); + }); }); type renderProps = { diff --git a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx index e2c9d1a17a2..06cc3ee84dd 100644 --- a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx @@ -4,7 +4,7 @@ import { useAppContext } from '../../hooks'; import classes from './LayoutSetsContainer.module.css'; import { ExportForm } from './ExportForm'; import { shouldDisplayFeature, FeatureFlag } from 'app-shared/utils/featureToggleUtils'; -import { StudioCombobox } from '@studio/components'; +import { StudioCombobox, StudioErrorMessage } from '@studio/components'; import { DeleteSubformWrapper } from './Subform/DeleteSubformWrapper'; import { useLayoutSetsExtendedQuery } from 'app-shared/hooks/queries/useLayoutSetsExtendedQuery'; import { getLayoutSetTypeTranslationKey } from 'app-shared/utils/layoutSetsUtils'; @@ -42,6 +42,10 @@ export function LayoutSetsContainer() { } }; + if (!layoutSets.sets.some((layoutSet) => layoutSet.id === selectedFormLayoutSetName)) { + return <StudioErrorMessage error={true}>{t('general.fetch_error_message')}</StudioErrorMessage>; + } + return ( <div className={classes.root}> <StudioCombobox diff --git a/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.module.css b/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.module.css index 82b0434e9bf..93dd6764ae3 100644 --- a/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.module.css +++ b/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.module.css @@ -1,25 +1,7 @@ -.container { - display: grid; - gap: var(--studio-property-vertical-gap); -} - .switch { padding-left: var(--fds-spacing-5); } -.dataModelBindings { - display: flex; - flex-direction: column; - align-items: flex-start; -} - .alert { margin: 0 var(--fds-spacing-5); } - -.wrapper { - padding-top: var(--fds-spacing-2); - padding-bottom: var(--fds-spacing-2); - text-overflow: ellipsis; - overflow: hidden; -} diff --git a/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.test.tsx b/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.test.tsx index fb1c2d4373d..57d4ade443d 100644 --- a/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.test.tsx @@ -17,7 +17,7 @@ import { layoutSet1NameMock } from '@altinn/ux-editor/testing/layoutSetsMock'; import { app, org } from '@studio/testing/testids'; import type { DataModelMetadataResponse } from 'app-shared/types/api'; -const defaultModel = 'testModel'; +const defaultModel = 'testModelField'; const defaultDataModel = 'testModel'; const secondDataModel = 'secondDataModel'; diff --git a/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.tsx b/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.tsx index 10ef68cbbe0..f6212906d67 100644 --- a/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/DataModelBindings.tsx @@ -68,7 +68,7 @@ export const DataModelBindings = (): React.JSX.Element => { return ( dataModelBindingsProperties && ( - <div className={classes.container}> + <> {(formItem.type === ComponentType.FileUploadWithTag || formItem.type === ComponentType.FileUpload) && isItemChildOfContainer(layout, formItem.id, ComponentType.RepeatingGroup) && ( @@ -86,33 +86,26 @@ export const DataModelBindings = (): React.JSX.Element => { {t('ux_editor.modal_properties_data_model_link_multiple_attachments')} </Switch> )} - <div className={classes.wrapper}> - <StudioProperty.Group> - {Object.keys(dataModelBindingsProperties).map((propertyKey: string) => { - return ( - <div - className={classes.dataModelBindings} - key={`${formItem.id}-data-model-${propertyKey}`} - > - <EditDataModelBinding - component={formItem} - handleComponentChange={async (updatedComponent, mutateOptions) => { - handleUpdate(updatedComponent); - debounceSave(formItemId, updatedComponent, mutateOptions); - }} - editFormId={formItemId} - helpText={dataModelBindingsProperties[propertyKey]?.description} - renderOptions={{ - key: propertyKey, - label: propertyKey !== 'simpleBinding' ? propertyKey : undefined, - }} - /> - </div> - ); - })} - </StudioProperty.Group> - </div> - </div> + <StudioProperty.Group> + {Object.keys(dataModelBindingsProperties).map((propertyKey: string) => { + return ( + <EditDataModelBinding + key={`${formItem.id}-data-model-${propertyKey}`} + component={formItem} + handleComponentChange={async (updatedComponent, mutateOptions) => { + handleUpdate(updatedComponent); + debounceSave(formItemId, updatedComponent, mutateOptions); + }} + editFormId={formItemId} + renderOptions={{ + key: propertyKey, + label: propertyKey !== 'simpleBinding' ? propertyKey : undefined, + }} + /> + ); + })} + </StudioProperty.Group> + </> ) ); }; diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/DefinedLayoutSet/DefinedLayoutSet.module.css b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/DefinedLayoutSet/DefinedLayoutSet.module.css deleted file mode 100644 index 2647e5af726..00000000000 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/DefinedLayoutSet/DefinedLayoutSet.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.selectedLayoutSet { - display: block; - padding: var(--fds-spacing-3); -} diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/DefinedLayoutSet/DefinedLayoutSet.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/DefinedLayoutSet/DefinedLayoutSet.tsx index b5a49eb1272..8368827b671 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/DefinedLayoutSet/DefinedLayoutSet.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/DefinedLayoutSet/DefinedLayoutSet.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { ClipboardIcon } from '@studio/icons'; import { StudioProperty } from '@studio/components'; import { useTranslation } from 'react-i18next'; -import classes from './DefinedLayoutSet.module.css'; type DefinedLayoutSetProps = { existingLayoutSetForSubform: string; @@ -13,8 +12,6 @@ export const DefinedLayoutSet = ({ existingLayoutSetForSubform }: DefinedLayoutS return ( <StudioProperty.Button - className={classes.selectedLayoutSet} - color='second' icon={<ClipboardIcon />} aria-label={t('ux_editor.component_properties.subform.selected_layout_set_title', { subform: existingLayoutSetForSubform, diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.module.css b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.module.css index 1e46d5356e6..e32a8f4215c 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.module.css +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.module.css @@ -1,3 +1,4 @@ .navigateSubformButton { margin-left: var(--fds-spacing-3); + margin-bottom: var(--fds-spacing-3); } diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/PropertiesHeader.module.css b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/PropertiesHeader.module.css index 0c958143711..06c8f1a0bd0 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/PropertiesHeader.module.css +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/PropertiesHeader.module.css @@ -3,6 +3,5 @@ flex-direction: column; background-color: var(--fds-semantic-surface-neutral-default); gap: var(--fds-spacing-2); - padding-bottom: var(--fds-spacing-2); align-items: flex-start; } diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css index 2cd7aa5f8af..f4fa61514f5 100644 --- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css +++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css @@ -4,7 +4,6 @@ .button { margin: var(--fds-spacing-1); - align-items: center; padding-left: 0; } diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx index 39f6c9a0155..7417695bb5a 100644 --- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx +++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx @@ -45,7 +45,7 @@ export const FormComponentConfig = ({ if (!schema?.properties) return null; const { properties } = schema; - const { hasCustomFileEndings, validFileEndings, grid, layoutSet } = properties; + const { hasCustomFileEndings, grid, layoutSet } = properties; // Add any properties that have a custom implementation to this list so they are not duplicated in the generic view const customProperties = [ @@ -185,7 +185,6 @@ export const FormComponentConfig = ({ component={component} handleComponentChange={handleComponentUpdate} propertyKey='validFileEndings' - helpText={validFileEndings?.description} /> )} </> @@ -199,7 +198,6 @@ export const FormComponentConfig = ({ handleComponentChange={handleComponentUpdate} propertyKey={propertyKey} key={propertyKey} - helpText={properties[propertyKey]?.description} enumValues={properties[propertyKey]?.enum || properties[propertyKey]?.examples} /> ); @@ -213,7 +211,6 @@ export const FormComponentConfig = ({ handleComponentChange={handleComponentUpdate} propertyKey={propertyKey} key={propertyKey} - helpText={t('ux_editor.component_properties.preselected_help_text')} enumValues={properties[propertyKey]?.enum} /> ); @@ -227,7 +224,6 @@ export const FormComponentConfig = ({ handleComponentChange={handleComponentUpdate} propertyKey={propertyKey} key={propertyKey} - helpText={properties[propertyKey]?.description} enumValues={properties[propertyKey]?.items?.enum} multiple={true} /> diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2Override.test.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2Override.test.tsx index 342b2ed0845..3b74d0c40e1 100644 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2Override.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2Override.test.tsx @@ -43,7 +43,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); expect( screen.getByRole('combobox', { - name: textMock('ux_editor.component_properties.overrides_type'), + name: textMock('ux_editor.component_properties.summary.override.display_type'), }), ).toBeInTheDocument(); }); @@ -54,7 +54,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); expect( screen.getByRole('combobox', { - name: textMock('ux_editor.component_properties.overrides_type'), + name: textMock('ux_editor.component_properties.summary.override.display_type'), }), ).toBeInTheDocument(); }); @@ -64,7 +64,7 @@ describe('Summary2Override', () => { await userEvent.click(addNewOverrideButton()); expect( screen.queryByRole('combobox', { - name: textMock('ux_editor.component_properties.overrides_type'), + name: textMock('ux_editor.component_properties.summary.override.display_type'), }), ).not.toBeInTheDocument(); }); @@ -113,7 +113,6 @@ describe('Summary2Override', () => { ), ); }); - it('should be able to change override hideEmptyFields', async () => { const user = userEvent.setup(); render({ overrides: [{ componentId: '1' }] }); @@ -133,7 +132,7 @@ describe('Summary2Override', () => { const user = userEvent.setup(); render({ overrides: [{ componentId: container1IdMock, isCompact: false }] }); const compactCheckbox = screen.getByRole('checkbox', { - name: textMock('ux_editor.component_properties.overrides_is_compact'), + name: textMock('ux_editor.component_properties.summary.override.is_compact'), }); expect(compactCheckbox).toBeInTheDocument(); expect(compactCheckbox).not.toBeChecked(); @@ -149,7 +148,7 @@ describe('Summary2Override', () => { const user = userEvent.setup(); render({ overrides: [{ componentId: container1IdMock, isCompact: true }] }); const compactCheckbox = screen.getByRole('checkbox', { - name: textMock('ux_editor.component_properties.overrides_is_compact'), + name: textMock('ux_editor.component_properties.summary.override.is_compact'), }); expect(compactCheckbox).toBeInTheDocument(); expect(compactCheckbox).toBeChecked(); @@ -166,17 +165,17 @@ describe('Summary2Override', () => { await userEvent.click(addNewOverrideButton()); expect( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_list'), + name: textMock('ux_editor.component_properties.summary.override.display_type.list'), }), ).toBeInTheDocument(); expect( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_string'), + name: textMock('ux_editor.component_properties.summary.override.display_type.string'), }), ).toBeInTheDocument(); expect( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_not_set'), + name: textMock('ux_editor.component_properties.summary.override.display_type.not_set'), }), ).toBeInTheDocument(); }); @@ -187,7 +186,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); await user.click( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_list'), + name: textMock('ux_editor.component_properties.summary.override.display_type.list'), }), ); await waitFor(() => @@ -203,7 +202,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); await user.click( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_string'), + name: textMock('ux_editor.component_properties.summary.override.display_type.string'), }), ); await waitFor(() => @@ -219,7 +218,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); await user.click( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_not_set'), + name: textMock('ux_editor.component_properties.summary.override.display_type.not_set'), }), ); await waitFor(() => @@ -235,7 +234,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); await user.click( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_list'), + name: textMock('ux_editor.component_properties.summary.override.display_type.list'), }), ); await waitFor(() => @@ -251,7 +250,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); await user.click( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_string'), + name: textMock('ux_editor.component_properties.summary.override.display_type.string'), }), ); await waitFor(() => @@ -267,7 +266,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); await user.click( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_not_set'), + name: textMock('ux_editor.component_properties.summary.override.display_type.not_set'), }), ); await waitFor(() => @@ -283,7 +282,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); await user.click( screen.getByRole('option', { - name: textMock('ux_editor.component_properties.overrides_string'), + name: textMock('ux_editor.component_properties.summary.override.display_type.string'), }), ); await waitFor(() => @@ -299,7 +298,7 @@ describe('Summary2Override', () => { await user.click(addNewOverrideButton()); const select = screen.getByRole('combobox', { - name: textMock('ux_editor.component_properties.overrides_type'), + name: textMock('ux_editor.component_properties.summary.override.display_type'), }); await user.selectOptions(select, 'list'); await waitFor(() => @@ -338,7 +337,7 @@ const removeOverrideButton = () => screen.getByRole('button', { name: '' }); const overrideComponentSelect = () => screen.getByRole('combobox', { - name: textMock('ux_editor.component_properties.summary.override.component_id'), + name: textMock('ux_editor.component_properties.summary.override.choose_component'), }); const defaultProps = { diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2Override.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2Override.tsx index 67a5a582ecc..e9d90fea4a1 100644 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2Override.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2Override.tsx @@ -40,7 +40,7 @@ export const Summary2Override = ({ overrides, onChange }: Summary2OverrideProps) <StudioHeading size='2xs'>{t('ux_editor.component_properties.overrides')}</StudioHeading> </StudioCard.Header> <StudioParagraph size='sm'> - {t('ux_editor.component_properties.overrides_description')} + {t('ux_editor.component_properties.summary.override.description')} </StudioParagraph> <StudioCard.Content> {overrides && diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2OverrideDisplayType.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2OverrideDisplayType.tsx index ef11f3f0124..67a3336bff9 100644 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2OverrideDisplayType.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2OverrideDisplayType.tsx @@ -29,7 +29,7 @@ export const Summary2OverrideDisplayType = ({ <StudioCard.Content> <StudioNativeSelect size='sm' - label={t('ux_editor.component_properties.overrides_type')} + label={t('ux_editor.component_properties.summary.override.display_type')} onChange={handleCustomTypeChange} value={override.displayType || 'string'} > diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2OverrideEntry.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2OverrideEntry.tsx index 3e20cc35869..7e2e38aace6 100644 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2OverrideEntry.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/Summary2OverrideEntry.tsx @@ -51,7 +51,7 @@ export const Summary2OverrideEntry = ({ return ( <> <Summmary2ComponentReferenceSelector - label={t('ux_editor.component_properties.summary.override.component_id')} + label={t('ux_editor.component_properties.summary.override.choose_component')} value={override.componentId} options={componentOptions} onValueChange={(value) => onChangeOverride('componentId', value)} @@ -125,7 +125,7 @@ const ComponentInGroupCheckbox = ({ checked={override.isCompact ?? false} value='isCompact' > - {t('ux_editor.component_properties.overrides_is_compact')} + {t('ux_editor.component_properties.summary.override.is_compact')} </Checkbox> ); }; diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/hook/useCustomConfigType.ts b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/hook/useCustomConfigType.ts index 0a1973889d7..f74f4e4908d 100644 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/hook/useCustomConfigType.ts +++ b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Override/hook/useCustomConfigType.ts @@ -8,8 +8,17 @@ export type CustomConfigType = { export const useCustomConfigType = (): CustomConfigType[] => { const { t } = useTranslation(); return [ - { value: 'string', label: t('ux_editor.component_properties.overrides_string') }, - { value: 'list', label: t('ux_editor.component_properties.overrides_list') }, - { value: 'notSet', label: t('ux_editor.component_properties.overrides_not_set') }, + { + value: 'string', + label: t('ux_editor.component_properties.summary.override.display_type.string'), + }, + { + value: 'list', + label: t('ux_editor.component_properties.summary.override.display_type.list'), + }, + { + value: 'notSet', + label: t('ux_editor.component_properties.summary.override.display_type.not_set'), + }, ]; }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.module.css index 8d5a9fd2850..8b90ffe1e8f 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.module.css +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.module.css @@ -1,14 +1,3 @@ -.selectedOption { - align-items: center; - display: flex; - gap: var(--fds-spacing-1); -} - .error { background-color: var(--fds-semantic-surface-danger-subtle); } - -.currentLinkedDataModel { - text-overflow: ellipsis; - overflow: hidden; -} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.test.tsx index da40a40c97d..5b613ec6bd7 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.test.tsx @@ -6,6 +6,8 @@ import { screen, waitForElementToBeRemoved, within } from '@testing-library/reac import { textMock } from '@studio/testing/mocks/i18nMock'; import type { QueryClient } from '@tanstack/react-query'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; +import { QueryKey } from 'app-shared/types/QueryKey'; +import { app, org } from '@studio/testing/testids'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; const label = 'label'; @@ -13,7 +15,7 @@ const dataModelField = 'field'; const dataModel = 'model'; const bindingKey = 'bindingKey'; -const defaultDefinedBinding: DefinedBindingProps = { +const defaultDefinedBindingProps: DefinedBindingProps = { label, onClick: jest.fn(), internalBindingFormat: { @@ -25,19 +27,19 @@ const defaultDefinedBinding: DefinedBindingProps = { }; type RenderDefinedBinding = { - props?: DefinedBindingProps; + props?: Partial<DefinedBindingProps>; queryClient?: QueryClient; queries?: Partial<ServicesContextProps>; }; const renderDefinedBinding = ({ - props = defaultDefinedBinding, + props, queryClient = createQueryClientMock(), queries, -}: RenderDefinedBinding) => { +}: RenderDefinedBinding = {}) => { return { - ...renderWithProviders(<DefinedBinding {...props} />, { - queries: { ...queries }, + ...renderWithProviders(<DefinedBinding {...defaultDefinedBindingProps} {...props} />, { + queries, queryClient, }), }; @@ -49,7 +51,7 @@ describe('DefinedBinding', () => { }); it('should render loading spinner', async () => { - renderDefinedBinding({}); + renderDefinedBinding(); const loadingSpinnerTitle = textMock('ux_editor.modal_properties_loading'); const loadingSpinner = screen.getByTitle(loadingSpinnerTitle); @@ -59,7 +61,7 @@ describe('DefinedBinding', () => { }); it('should render edit button with the binding selected', async () => { - renderDefinedBinding({}); + renderDefinedBinding(); await waitForElementToBeRemoved(() => screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), @@ -77,7 +79,6 @@ describe('DefinedBinding', () => { it('should render edit button with the binding selected even with no data model selected', async () => { renderDefinedBinding({ props: { - ...defaultDefinedBinding, internalBindingFormat: { field: dataModelField, dataType: '', @@ -97,4 +98,48 @@ describe('DefinedBinding', () => { const editButtonText = within(editButton).getByText(dataModelField); expect(editButtonText).toBeInTheDocument(); }); + + it('should render with error css class when selected data model does not exist', async () => { + renderWithDataModelAndMetadata('non-existent-model', dataModelField); + await waitForElementToBeRemoved(() => + screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), + ); + const editButton = screen.getByRole('button', { + name: textMock('right_menu.data_model_bindings_edit', { binding: label }), + }); + expect(editButton).toHaveClass('error'); + }); + + it('should render with error css class when selected data model field does not exist in model', async () => { + renderWithDataModelAndMetadata(dataModel, 'non-existent-field'); + await waitForElementToBeRemoved(() => + screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), + ); + const editButton = screen.getByRole('button', { + name: textMock('right_menu.data_model_bindings_edit', { binding: label }), + }); + expect(editButton).toHaveClass('error'); + }); + + it('should render without error css class when selected data model and field is valid', async () => { + renderWithDataModelAndMetadata(dataModel, dataModelField); + await waitForElementToBeRemoved(() => + screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), + ); + const editButton = screen.getByRole('button', { + name: textMock('right_menu.data_model_bindings_edit', { binding: label }), + }); + expect(editButton).not.toHaveClass('error'); + }); }); + +const renderWithDataModelAndMetadata = (modelName: string, fieldName: string) => { + const queryClient = createQueryClientMock(); + queryClient.setQueryData([QueryKey.AppMetadataModelIds, org, app, false], [modelName]); + const getDataModelMetadata = jest + .fn() + .mockImplementation(() => + Promise.resolve({ elements: { [fieldName]: { dataBindingName: fieldName, maxOccurs: 0 } } }), + ); + renderDefinedBinding({ queries: { getDataModelMetadata }, queryClient }); +}; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.tsx index cbc0f375037..5dc048992d4 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/DefinedBinding/DefinedBinding.tsx @@ -43,23 +43,17 @@ export const DefinedBinding = ({ const dataModelFields = getDataModelFields({ componentType, bindingKey, dataModelMetadata }); const isFieldValid = validateSelectedDataField(currentDataModelField, dataModelFields); - const isBindingError = !isFieldValid || !isDataModelValid; - const value = ( - <span className={classes.selectedOption}> - <LinkIcon /> <span className={classes.currentLinkedDataModel}>{currentDataModelField}</span> - </span> - ); - return ( <StudioProperty.Button - className={`${isBindingError ? classes.error : ''}`} + className={isBindingError ? classes.error : ''} aria-label={title} onClick={onClick} property={label} title={title} - value={value} + icon={<LinkIcon />} + value={currentDataModelField} /> ); }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/EditBinding.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/EditBinding.test.tsx index 98b5ff43d95..2db64183ed1 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/EditBinding.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/EditBinding.test.tsx @@ -8,7 +8,6 @@ import { ComponentType } from 'app-shared/types/ComponentType'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { screen, waitForElementToBeRemoved } from '@testing-library/react'; import { textMock } from '@studio/testing/mocks/i18nMock'; -import { typedLocalStorage } from '@studio/pure-functions'; import userEvent from '@testing-library/user-event'; import type { InternalBindingFormat } from '@altinn/ux-editor/utils/dataModelUtils'; import { layoutSet1NameMock } from '../../../../../testing/layoutSetsMock'; @@ -18,13 +17,13 @@ import { app, org } from '@studio/testing/testids'; const defaultLabel = 'label'; const defaultBindingKey = 'simpleBinding'; const defaultDataModelField = 'field1'; +const secondDataModelField = 'field2'; const defaultDataModel = 'defaultModel'; const secondDataModel = 'secondModel'; const defaultEditBinding: EditBindingProps = { bindingKey: defaultBindingKey, component: componentMocks[ComponentType.Input], - helpText: undefined, label: defaultLabel, handleComponentChange: jest.fn(), onSetDataModelSelectVisible: jest.fn(), @@ -43,9 +42,10 @@ const MockedParentComponent = (props: MockedParentComponentProps) => { <EditBinding {...props} handleComponentChange={(formItem) => { + const fieldBinding = formItem.dataModelBindings[defaultBindingKey] as InternalBindingFormat; setNewInternalBindingFormat((prev) => ({ ...prev, - field: formItem.dataModelBindings[defaultBindingKey], + field: fieldBinding.field, })); }} internalBindingFormat={newInternalBindingFormat} @@ -97,7 +97,7 @@ const getDataModelMetadataMock = jest .fn() .mockImplementation(() => Promise.resolve(dataModelMetadataResponseMock)); -describe('EditBinding without featureFlag', () => { +describe('EditBinding', () => { it('should render loading spinner', async () => { renderEditBinding({}); @@ -120,7 +120,7 @@ describe('EditBinding without featureFlag', () => { expect(fieldSet).toBeInTheDocument(); }); - it('should render correct elements in field set', async () => { + it('should display two selectors: data model and a data model field', async () => { renderEditBinding({ queries: { getAppMetadataModelIds: getAppMetadataModelIdsMock, @@ -132,18 +132,15 @@ describe('EditBinding without featureFlag', () => { screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), ); - const label = screen.getByText(defaultEditBinding.label); - expect(label).toBeInTheDocument(); - - const selectedModel = screen.getByText(defaultDataModel); - const noneExistingDataModelSelector = screen.queryByRole('combobox', { + const dataModelSelector = screen.getByRole('combobox', { name: textMock('ux_editor.modal_properties_data_model_binding'), }); - expect(selectedModel).toBeInTheDocument(); - expect(noneExistingDataModelSelector).not.toBeInTheDocument(); + expect(dataModelSelector).toBeInTheDocument(); - const selectedField = screen.getByRole('combobox'); - expect(selectedField).toBeInTheDocument(); + const dataModelFieldSelector = screen.getByRole('combobox', { + name: textMock('ux_editor.modal_properties_data_model_field_binding'), + }); + expect(dataModelFieldSelector).toBeInTheDocument(); }); it('should display default data model and "choose datafield" when no bindings', async () => { @@ -165,7 +162,10 @@ describe('EditBinding without featureFlag', () => { screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), ); - expect(screen.getByText(defaultDataModel)).toBeInTheDocument(); + const dataModelSelector = screen.getByRole('combobox', { + name: textMock('ux_editor.modal_properties_data_model_binding'), + }); + expect(dataModelSelector).toHaveValue(defaultDataModel); const chooseDataFieldOption: HTMLOptionElement = screen.getByRole('option', { name: textMock('ux_editor.modal_properties_data_model_field_choose'), @@ -226,14 +226,15 @@ describe('EditBinding without featureFlag', () => { ); expect(errorMessage).toBeInTheDocument(); - const dataModelFieldSelector = screen.getByRole('combobox'); - const option2 = screen.getByRole('option', { name: defaultDataModelField }); + const dataModelFieldSelector = screen.getByRole('combobox', { + name: textMock('ux_editor.modal_properties_data_model_field_binding'), + }); + const option2 = screen.getByRole('option', { name: secondDataModelField }); await user.selectOptions(dataModelFieldSelector, option2); - expect(errorMessage).not.toBeInTheDocument(); }); - it('should call handleComponentChange with old binding format when data model field is changed', async () => { + it('should call handleComponentChange with new binding format when data model field is changed', async () => { const user = userEvent.setup(); const handleComponentChange = jest.fn(); renderEditBinding({ @@ -251,7 +252,9 @@ describe('EditBinding without featureFlag', () => { screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), ); - const dataModelFieldSelector = screen.getByRole('combobox'); + const dataModelFieldSelector = screen.getByRole('combobox', { + name: textMock('ux_editor.modal_properties_data_model_field_binding'), + }); const option2 = screen.getByRole('option', { name: 'field2' }); await user.selectOptions(dataModelFieldSelector, option2); @@ -260,7 +263,10 @@ describe('EditBinding without featureFlag', () => { { ...componentMocks[ComponentType.Input], dataModelBindings: { - [defaultEditBinding.bindingKey]: 'field2', + [defaultEditBinding.bindingKey]: { + field: 'field2', + dataType: defaultDataModel, + }, }, maxCount: undefined, required: true, @@ -272,8 +278,7 @@ describe('EditBinding without featureFlag', () => { ); }); - it('should set the data model binding to the default value when click on delete button', async () => { - window.confirm = jest.fn(() => true); + it('should call handleComponentChange with new binding format when data model is changed', async () => { const user = userEvent.setup(); const handleComponentChange = jest.fn(); renderEditBinding({ @@ -291,17 +296,21 @@ describe('EditBinding without featureFlag', () => { screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), ); - const deleteButton = screen.getByRole('button', { - name: textMock('general.delete'), + const dataModelSelector = screen.getByRole('combobox', { + name: textMock('ux_editor.modal_properties_data_model_binding'), }); - await user.click(deleteButton); + const option2 = screen.getByRole('option', { name: secondDataModel }); + await user.selectOptions(dataModelSelector, option2); expect(handleComponentChange).toHaveBeenCalledTimes(1); expect(handleComponentChange).toHaveBeenCalledWith( { ...componentMocks[ComponentType.Input], dataModelBindings: { - simpleBinding: '', + [defaultEditBinding.bindingKey]: { + field: '', + dataType: secondDataModel, + }, }, maxCount: undefined, required: undefined, @@ -313,14 +322,13 @@ describe('EditBinding without featureFlag', () => { ); }); - it('should delete the data model binding when click on delete button and no default value is defined', async () => { + it('should call handleComponentChange when click on delete button', async () => { window.confirm = jest.fn(() => true); const user = userEvent.setup(); const handleComponentChange = jest.fn(); renderEditBinding({ editBindingProps: { ...defaultEditBinding, - component: componentMocks[ComponentType.FileUpload], handleComponentChange, }, queries: { @@ -338,120 +346,12 @@ describe('EditBinding without featureFlag', () => { }); await user.click(deleteButton); - expect(handleComponentChange).toHaveBeenCalledTimes(1); - expect(handleComponentChange).toHaveBeenCalledWith(componentMocks[ComponentType.FileUpload], { - onSuccess: expect.any(Function), - }); - }); -}); - -describe('EditBinding with featureFlag', () => { - beforeEach(() => { - typedLocalStorage.removeItem('featureFlags'); - }); - it('should display two selectors: data model and a data model field, when the feature flag is enabled', async () => { - typedLocalStorage.setItem<string[]>('featureFlags', ['multipleDataModelsPerTask']); - renderEditBinding({ - queries: { - getAppMetadataModelIds: getAppMetadataModelIdsMock, - getDataModelMetadata: getDataModelMetadataMock, - }, - }); - - await waitForElementToBeRemoved(() => - screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), - ); - - const dataModelSelector = screen.getByRole('combobox', { - name: textMock('ux_editor.modal_properties_data_model_binding'), - }); - expect(dataModelSelector).toBeInTheDocument(); - - const dataModelFieldSelector = screen.getByRole('combobox', { - name: textMock('ux_editor.modal_properties_data_model_field_binding'), - }); - expect(dataModelFieldSelector).toBeInTheDocument(); - }); - - it('should call handleComponentChange with new binding format when data model field is changed', async () => { - typedLocalStorage.setItem<string[]>('featureFlags', ['multipleDataModelsPerTask']); - const user = userEvent.setup(); - const handleComponentChange = jest.fn(); - renderEditBinding({ - editBindingProps: { - ...defaultEditBinding, - handleComponentChange, - }, - queries: { - getAppMetadataModelIds: getAppMetadataModelIdsMock, - getDataModelMetadata: getDataModelMetadataMock, - }, - }); - - await waitForElementToBeRemoved(() => - screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), - ); - - const dataModelFieldSelector = screen.getByRole('combobox', { - name: textMock('ux_editor.modal_properties_data_model_field_binding'), - }); - const option2 = screen.getByRole('option', { name: 'field2' }); - await user.selectOptions(dataModelFieldSelector, option2); - - expect(handleComponentChange).toHaveBeenCalledTimes(1); - expect(handleComponentChange).toHaveBeenCalledWith( - { - ...componentMocks[ComponentType.Input], - dataModelBindings: { - [defaultEditBinding.bindingKey]: { - field: 'field2', - dataType: defaultDataModel, - }, - }, - maxCount: undefined, - required: true, - timeStamp: undefined, - }, - { - onSuccess: expect.any(Function), - }, - ); - }); - - it('should call handleComponentChange with new binding format when data model is changed', async () => { - typedLocalStorage.setItem<string[]>('featureFlags', ['multipleDataModelsPerTask']); - const user = userEvent.setup(); - const handleComponentChange = jest.fn(); - renderEditBinding({ - editBindingProps: { - ...defaultEditBinding, - handleComponentChange, - }, - queries: { - getAppMetadataModelIds: getAppMetadataModelIdsMock, - getDataModelMetadata: getDataModelMetadataMock, - }, - }); - - await waitForElementToBeRemoved(() => - screen.queryByTitle(textMock('ux_editor.modal_properties_loading')), - ); - - const dataModelSelector = screen.getByRole('combobox', { - name: textMock('ux_editor.modal_properties_data_model_binding'), - }); - const option2 = screen.getByRole('option', { name: secondDataModel }); - await user.selectOptions(dataModelSelector, option2); - expect(handleComponentChange).toHaveBeenCalledTimes(1); expect(handleComponentChange).toHaveBeenCalledWith( { ...componentMocks[ComponentType.Input], dataModelBindings: { - [defaultEditBinding.bindingKey]: { - field: '', - dataType: secondDataModel, - }, + simpleBinding: '', }, maxCount: undefined, required: undefined, diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/EditBinding.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/EditBinding.tsx index 2c88efc3155..330b6ca7c43 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/EditBinding.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/EditBinding.tsx @@ -10,7 +10,6 @@ import { getXsdDataTypeFromDataModelFields, type InternalBindingFormat, } from '@altinn/ux-editor/utils/dataModelUtils'; -import { shouldDisplayFeature, FeatureFlag } from 'app-shared/utils/featureToggleUtils'; import { useAppContext } from '@altinn/ux-editor/hooks'; import type { UpdateFormMutateOptions } from '@altinn/ux-editor/containers/FormItemContext'; import { EditBindingButtons } from './EditBindingButtons'; @@ -22,7 +21,6 @@ import { formItemConfigs } from '@altinn/ux-editor/data/formItemConfig'; export type EditBindingProps = { bindingKey: string; component: FormItem; - helpText: string; label: string; handleComponentChange: (component: FormItem, mutateOptions?: UpdateFormMutateOptions) => void; onSetDataModelSelectVisible: (visible: boolean) => void; @@ -32,7 +30,6 @@ export type EditBindingProps = { export const EditBinding = ({ bindingKey, component, - helpText, label, handleComponentChange, onSetDataModelSelectVisible, @@ -48,9 +45,7 @@ export const EditBinding = ({ const selectedDataFieldElement = updatedBinding?.field; const value = - (shouldDisplayFeature(FeatureFlag.MultipleDataModelsPerTask) - ? updatedBinding - : selectedDataFieldElement) ?? + updatedBinding ?? formItemConfigs[component.type]?.defaultProperties?.['dataModelBindings']?.[bindingKey]; const dataModelBindings = { ...component.dataModelBindings }; @@ -102,7 +97,6 @@ export const EditBinding = ({ internalBindingFormat={internalBindingFormat} handleBindingChange={handleBindingChange} bindingKey={bindingKey} - helpText={helpText} componentType={component.type} /> <EditBindingButtons diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/SelectDataFieldBinding.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/SelectDataFieldBinding.tsx index 3e2f897c4c2..37f43fbf79c 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/SelectDataFieldBinding.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/SelectDataFieldBinding.tsx @@ -10,12 +10,12 @@ import { StudioNativeSelect } from '@studio/components'; import { useValidDataModels } from '@altinn/ux-editor/hooks/useValidDataModels'; import type { ComponentType } from 'app-shared/types/ComponentType'; import classes from './SelectDataFieldBinding.module.css'; +import { useComponentPropertyHelpText } from '../../../../../hooks'; type SelectDataFieldProps = { internalBindingFormat: InternalBindingFormat; handleBindingChange: (dataModelBindings: InternalBindingFormat) => void; bindingKey: string; - helpText: string; componentType: ComponentType; }; @@ -23,7 +23,6 @@ export const SelectDataFieldBinding = ({ internalBindingFormat, handleBindingChange, bindingKey, - helpText, componentType, }: SelectDataFieldProps): React.JSX.Element => { const { t } = useTranslation(); @@ -35,6 +34,7 @@ export const SelectDataFieldBinding = ({ const dataModelFields = getDataModelFields({ componentType, bindingKey, dataModelMetadata }); const isDataModelFieldValid = validateSelectedDataField(currentDataModelField, dataModelFields); + const componentPropertyHelpText = useComponentPropertyHelpText(); // Validate datamodel as well: fallbacks to default if invalid, then user must update datafield const isBindingError = !isDataModelFieldValid || !isDataModelValid; @@ -57,7 +57,7 @@ export const SelectDataFieldBinding = ({ onChange={handleDataModelFieldChange} value={isBindingError ? '' : currentDataModelField} propertyPath={propertyPath} - helpText={helpText} + helpText={componentPropertyHelpText(`data_model_bindings.${bindingKey}`)} label={t('ux_editor.modal_properties_data_model_field_binding')} renderField={({ fieldProps }) => ( <StudioNativeSelect diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/SelectDataModelBinding.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/SelectDataModelBinding.tsx index 379cca53608..4888002b749 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/SelectDataModelBinding.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditBinding/SelectDataModelBinding.tsx @@ -1,8 +1,7 @@ import React, { useId } from 'react'; import classes from './SelectDataModelBinding.module.css'; import { FormField } from 'app-shared/components/FormField'; -import { shouldDisplayFeature, FeatureFlag } from 'app-shared/utils/featureToggleUtils'; -import { StudioDisplayTile, StudioNativeSelect } from '@studio/components'; +import { StudioNativeSelect } from '@studio/components'; import { useTranslation } from 'react-i18next'; import type { InternalBindingFormat } from '@altinn/ux-editor/utils/dataModelUtils'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; @@ -41,7 +40,7 @@ export const SelectDataModelBinding = ({ handleBindingChange(dataModelBinding); }; - return shouldDisplayFeature(FeatureFlag.MultipleDataModelsPerTask) ? ( + return ( <FormField id={id} onChange={handleDataModelChange} @@ -70,10 +69,5 @@ export const SelectDataModelBinding = ({ </StudioNativeSelect> )} /> - ) : ( - <StudioDisplayTile - label={t('ux_editor.modal_properties_data_model_binding')} - value={selectedDataModel} - /> ); }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditDataModelBinding.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditDataModelBinding.test.tsx index 97f849eb6c1..a1915c1425c 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditDataModelBinding.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditDataModelBinding.test.tsx @@ -20,7 +20,6 @@ const defaultEditDataModelingBinding: EditDataModelBindingProps<any> = { returnValue: 'returnValue', key: 'key', }, - helpText: 'helpText', }; type renderEditDataModelBinding = { @@ -62,7 +61,9 @@ describe('EditDataModelBinding', () => { await user.click(bindingButton); expect(bindingButton).not.toBeInTheDocument(); - const dataModelFieldSelector = screen.getByRole('combobox'); + const dataModelFieldSelector = screen.getByRole('combobox', { + name: textMock('ux_editor.modal_properties_data_model_field_binding'), + }); expect(dataModelFieldSelector).toBeInTheDocument(); }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditDataModelBinding.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditDataModelBinding.tsx index cf57b1763df..f813d5fd348 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditDataModelBinding.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditDataModelBinding/EditDataModelBinding.tsx @@ -15,14 +15,12 @@ export interface EditDataModelBindingProps<T extends ComponentType> returnValue?: any; key?: string; }; - helpText?: string; } export const EditDataModelBinding = <T extends ComponentType>({ component, handleComponentChange, renderOptions, - helpText, }: EditDataModelBindingProps<T>) => { const { key, label } = renderOptions || {}; const bindingKey = key || 'simpleBinding'; @@ -46,7 +44,6 @@ export const EditDataModelBinding = <T extends ComponentType>({ <EditBinding bindingKey={bindingKey} component={component} - helpText={helpText} label={labelSpecificText} handleComponentChange={handleComponentChange} onSetDataModelSelectVisible={setDataModelSelectVisible} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditNumberValue.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditNumberValue.tsx index ca444e84798..eddb4d4a176 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditNumberValue.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditNumberValue.tsx @@ -6,7 +6,11 @@ import { StudioDecimalInput, StudioNativeSelect } from '@studio/components'; import type { ComponentType } from 'app-shared/types/ComponentType'; import type { FormItem } from '../../../types/FormItem'; import type { FilterKeysOfType } from 'app-shared/types/FilterKeysOfType'; -import { useComponentPropertyLabel, useAppContext } from '../../../hooks'; +import { + useComponentPropertyLabel, + useAppContext, + useComponentPropertyHelpText, +} from '../../../hooks'; import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs'; import { useTranslation } from 'react-i18next'; @@ -15,7 +19,6 @@ type NumberKeys<ObjectType extends KeyValuePairs> = FilterKeysOfType<ObjectType, export interface EditNumberValueProps<T extends ComponentType, K extends NumberKeys<FormItem<T>>> extends IGenericEditComponent<T> { propertyKey: K; - helpText?: string; enumValues?: number[]; } @@ -23,12 +26,12 @@ export const EditNumberValue = <T extends ComponentType, K extends NumberKeys<Fo component, handleComponentChange, propertyKey, - helpText, enumValues, }: EditNumberValueProps<T, K>) => { const { t } = useTranslation(); const componentPropertyLabel = useComponentPropertyLabel(); const { selectedFormLayoutSetName, updateLayoutsForPreview } = useAppContext(); + const componentPropertyHelpText = useComponentPropertyHelpText(); const handleValueChange = async (newValue: number) => { handleComponentChange(setComponentProperty<T, number, K>(component, propertyKey, newValue), { @@ -45,7 +48,7 @@ export const EditNumberValue = <T extends ComponentType, K extends NumberKeys<Fo value={component[propertyKey]} onChange={handleValueChange} propertyPath={component.propertyPath} - helpText={helpText} + helpText={componentPropertyHelpText(String(propertyKey))} renderField={({ fieldProps }) => enumValues ? ( <StudioNativeSelect diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/LibraryOptionsEditor/LibraryOptionsEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/LibraryOptionsEditor/LibraryOptionsEditor.tsx index 416435c6078..975b4e8aff5 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/LibraryOptionsEditor/LibraryOptionsEditor.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/LibraryOptionsEditor/LibraryOptionsEditor.tsx @@ -1,4 +1,3 @@ -import type { Option } from 'app-shared/types/Option'; import React, { createRef } from 'react'; import { useTranslation } from 'react-i18next'; import { StudioCodeListEditor, StudioModal, StudioAlert } from '@studio/components'; @@ -12,27 +11,28 @@ import { OptionListLabels } from '../OptionListLabels'; import { hasOptionListChanged } from '../../../utils/optionsUtils'; import { useOptionListQuery } from 'app-shared/hooks/queries'; import classes from './LibraryOptionsEditor.module.css'; +import type { OptionList } from 'app-shared/types/OptionList'; type LibraryOptionsEditorProps = { handleDelete: () => void; - optionsId: string; + optionListId: string; }; export function LibraryOptionsEditor({ handleDelete, - optionsId, + optionListId, }: LibraryOptionsEditorProps): React.ReactNode { const { t } = useTranslation(); const { org, app } = useStudioEnvironmentParams(); - const { data: optionsList } = useOptionListQuery(org, app, optionsId); + const { data: optionList } = useOptionListQuery(org, app, optionListId); const { doReloadPreview } = usePreviewContext(); const { mutate: updateOptionList } = useUpdateOptionListMutation(org, app); const editorTexts: CodeListEditorTexts = useOptionListEditorTexts(); const modalRef = createRef<HTMLDialogElement>(); - const handleOptionsListChange = (options: Option[]) => { - if (hasOptionListChanged(optionsList, options)) { - updateOptionList({ optionListId: optionsId, optionsList: options }); + const handleOptionsListChange = (newOptionList: OptionList) => { + if (hasOptionListChanged(optionList, newOptionList)) { + updateOptionList({ optionListId, optionList: newOptionList }); doReloadPreview(); } }; @@ -43,7 +43,7 @@ export function LibraryOptionsEditor({ return ( <> - <OptionListLabels optionsId={optionsId} optionsList={optionsList} /> + <OptionListLabels optionListId={optionListId} optionList={optionList} /> <OptionListButtons handleClick={handleClick} handleDelete={handleDelete} /> <StudioModal.Dialog ref={modalRef} @@ -58,7 +58,7 @@ export function LibraryOptionsEditor({ } > <StudioCodeListEditor - codeList={optionsList} + codeList={optionList} onAddOrDeleteItem={handleOptionsListChange} onBlurAny={handleOptionsListChange} texts={editorTexts} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/ManualOptionsEditor/ManualOptionsEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/ManualOptionsEditor/ManualOptionsEditor.tsx index a58f88d0e5b..1b0f2596143 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/ManualOptionsEditor/ManualOptionsEditor.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/ManualOptionsEditor/ManualOptionsEditor.tsx @@ -43,7 +43,7 @@ export const ManualOptionsEditor = forwardRef<HTMLDialogElement, ManualOptionsEd return ( <> - <OptionListLabels optionsId={component.optionsId} optionsList={component.options} /> + <OptionListLabels optionListId={component.optionsId} optionList={component.options} /> <OptionListButtons handleDelete={handleDelete} handleClick={handleClick} /> <StudioModal.Dialog ref={modalRef} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.test.tsx index 2a222cc89bb..98761d9e310 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; -import type { OptionsList } from 'app-shared/types/api/OptionsLists'; +import type { OptionList } from 'app-shared/types/OptionList'; import type { Option } from 'app-shared/types/Option'; import { ComponentType } from 'app-shared/types/ComponentType'; import type { OptionListEditorProps } from './OptionListEditor'; @@ -17,14 +17,14 @@ import { componentMocks } from '../../../../../../../testing/componentMocks'; // Test data: const mockComponent = componentMocks[ComponentType.RadioButtons]; const handleComponentChange = jest.fn(); -const apiResult: OptionsList = [ +const apiResult: OptionList = [ { value: 'test', label: 'label text', description: 'description', helpText: 'help text' }, { value: 2, label: 'label number', description: null, helpText: null }, { value: true, label: 'label boolean', description: null, helpText: null }, ]; const getOptionListMock = jest .fn() - .mockImplementation(() => Promise.resolve<OptionsList>(apiResult)); + .mockImplementation(() => Promise.resolve<OptionList>(apiResult)); const componentWithOptionsId = { ...mockComponent, options: undefined, optionsId: 'some-id' }; describe('OptionListEditor', () => { @@ -123,7 +123,7 @@ describe('OptionListEditor', () => { it('should render a spinner when there is no data', () => { renderOptionListEditor({ queries: { - getOptionList: jest.fn().mockImplementation(() => Promise.resolve<OptionsList>([])), + getOptionList: jest.fn().mockImplementation(() => Promise.resolve<OptionList>([])), }, props: { component: componentWithOptionsId }, }); @@ -208,14 +208,14 @@ describe('OptionListEditor', () => { }); it('should show placeholder for option label when option list label is empty', async () => { - const apiResultWithEmptyLabel: OptionsList = [ + const apiResultWithEmptyLabel: OptionList = [ { value: true, label: '', description: null, helpText: null }, ]; await renderOptionListEditorAndWaitForSpinnerToBeRemoved({ queries: { getOptionList: jest .fn() - .mockImplementation(() => Promise.resolve<OptionsList>(apiResultWithEmptyLabel)), + .mockImplementation(() => Promise.resolve<OptionList>(apiResultWithEmptyLabel)), }, }); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx index 1ac15d0f3a3..caa7d3bd693 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx @@ -61,7 +61,7 @@ function OptionListResolver({ </StudioErrorMessage> ); case 'success': { - return <LibraryOptionsEditor handleDelete={handleDelete} optionsId={optionsId} />; + return <LibraryOptionsEditor handleDelete={handleDelete} optionListId={optionsId} />; } } } diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListLabels/OptionListLabels.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListLabels/OptionListLabels.tsx index dfe940d1210..79e378a4b1d 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListLabels/OptionListLabels.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListLabels/OptionListLabels.tsx @@ -2,22 +2,22 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { StudioParagraph } from '@studio/components'; import { useConcatOptionsLabels } from '../hooks/useConcatOptionsLabels'; -import type { OptionsList } from 'app-shared/types/api/OptionsLists'; +import type { OptionList } from 'app-shared/types/OptionList'; import classes from './OptionListLabels.module.css'; -type OptionListLabelsProps = { optionsList: OptionsList; optionsId: string }; +type OptionListLabelsProps = { optionList: OptionList; optionListId: string }; export function OptionListLabels({ - optionsList, - optionsId, + optionList, + optionListId, }: OptionListLabelsProps): React.ReactNode { const { t } = useTranslation(); - const codeListLabels: string = useConcatOptionsLabels(optionsList); + const codeListLabels: string = useConcatOptionsLabels(optionList); return ( <> <StudioParagraph size='sm' className={classes.label}> - {optionsId ?? t('ux_editor.modal_properties_code_list_custom_list')} + {optionListId ?? t('ux_editor.modal_properties_code_list_custom_list')} </StudioParagraph> <StudioParagraph size='sm' className={classes.codeListLabels} variant='short'> {codeListLabels} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/ReferenceTab/ReferenceTab.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/ReferenceTab/ReferenceTab.tsx index b5941823e09..e4d62b23302 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/ReferenceTab/ReferenceTab.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/ReferenceTab/ReferenceTab.tsx @@ -64,7 +64,9 @@ export function ReferenceTab({ <p> <Trans i18nKey={'ux_editor.modal_properties_code_list_read_more'}> <a - href={altinnDocsUrl({ relativeUrl: 'altinn-studio/guides/options/dynamic-codelists/' })} + href={altinnDocsUrl({ + relativeUrl: 'altinn-studio/guides/development/options/sources/dynamic/', + })} target='_newTab' rel='noopener noreferrer' /> diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/SelectTab/SelectTab.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/SelectTab/SelectTab.tsx index b7b7fb387e9..63f35ed39d5 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/SelectTab/SelectTab.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/SelectTab/SelectTab.tsx @@ -83,7 +83,7 @@ export function SelectTab<T extends SelectionComponentType>({ <a className={classes.linkStaticCodeLists} href={altinnDocsUrl({ - relativeUrl: 'altinn-studio/reference/data/options/static-codelists/', + relativeUrl: 'altinn-studio/guides/development/options/sources/static/', })} target='_newTab' rel='noopener noreferrer' diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/utils/optionsUtils.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/utils/optionsUtils.test.ts index f526fc0e625..e9485baccb4 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/utils/optionsUtils.test.ts +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/utils/optionsUtils.test.ts @@ -1,5 +1,5 @@ import { SelectedOptionsType } from '../../../../../../components/config/editModal/EditOptions/EditOptions'; -import type { OptionsList } from 'app-shared/types/api/OptionsLists'; +import type { OptionList } from 'app-shared/types/OptionList'; import { ComponentType } from 'app-shared/types/ComponentType'; import type { FormItem } from '../../../../../../types/FormItem'; import type { SelectionComponentType } from '../../../../../../types/FormComponent'; @@ -26,7 +26,7 @@ describe('optionsUtils', () => { describe('getSelectedOptionsType', () => { it('should return SelectedOptionsType.Unknown if both options and optionsId are set', () => { const codeListId = 'codeListId'; - const options: OptionsList = [{ label: 'label1', value: 'value1' }]; + const options: OptionList = [{ label: 'label1', value: 'value1' }]; const optionListIds = ['codeListId']; const result = getSelectedOptionsType(codeListId, options, optionListIds); expect(result).toEqual(SelectedOptionsType.Unknown); @@ -67,7 +67,7 @@ describe('optionsUtils', () => { describe('getSelectedOptionsTypeWithManualSupport', () => { it('should return SelectedOptionsType.Unknown if both options and optionsId are set', () => { const codeListId = 'codeListId'; - const options: OptionsList = [{ label: 'label1', value: 'value1' }]; + const options: OptionList = [{ label: 'label1', value: 'value1' }]; const optionListIds = ['codeListId']; const result = getSelectedOptionsTypeWithManualSupport(codeListId, options, optionListIds); expect(result).toEqual(SelectedOptionsType.Unknown); @@ -135,14 +135,14 @@ describe('optionsUtils', () => { describe('hasOptionListChanged', () => { it('should return false if the optionList has not changed', () => { - const oldOptions: OptionsList = [{ label: 'label1', value: 'value1' }]; - const newOptions: OptionsList = [{ label: 'label1', value: 'value1' }]; + const oldOptions: OptionList = [{ label: 'label1', value: 'value1' }]; + const newOptions: OptionList = [{ label: 'label1', value: 'value1' }]; expect(hasOptionListChanged(oldOptions, newOptions)).toEqual(false); }); it('should return true if the optionList has changed', () => { - const oldOptions: OptionsList = [{ label: 'label1', value: 'value1' }]; - const newOptions: OptionsList = [{ label: 'new label', value: 'new value' }]; + const oldOptions: OptionList = [{ label: 'label1', value: 'value1' }]; + const newOptions: OptionList = [{ label: 'new label', value: 'new value' }]; expect(hasOptionListChanged(oldOptions, newOptions)).toEqual(true); }); }); @@ -179,7 +179,7 @@ describe('optionsUtils', () => { describe('updateComponentOptions', () => { it('should update options on the returned object', () => { - const options: OptionsList = [{ label: 'new-label', value: 'new-value' }]; + const options: OptionList = [{ label: 'new-label', value: 'new-value' }]; expect(updateComponentOptions(mockedComponent, options)).toStrictEqual({ ...mockedComponent, optionsId: undefined, @@ -212,28 +212,28 @@ describe('optionsUtils', () => { it('should return true if options ID is a string and options ID is from library', () => { const optionListIds: string[] = ['test1', 'test2']; const optionsId: string = 'test1'; - const options: OptionsList = [{ value: 'value', label: 'label' }]; + const options: OptionList = [{ value: 'value', label: 'label' }]; expect(isOptionsModifiable(optionListIds, optionsId, options)).toEqual(true); }); it('should return true if options is set on the component', () => { const optionListIds: string[] = []; const optionsId = ''; - const options: OptionsList = []; + const options: OptionList = []; expect(isOptionsModifiable(optionListIds, optionsId, options)).toEqual(true); }); it('should return false if options ID and options are undefined', () => { const optionListIds: string[] = ['test1', 'test2']; const optionsId = undefined; - const options: OptionsList = undefined; + const options: OptionList = undefined; expect(isOptionsModifiable(optionListIds, optionsId, options)).toEqual(false); }); it('should return false if options ID is not from library', () => { const optionListIds: string[] = ['test1', 'test2']; const optionsId = 'another-id'; - const options: OptionsList = undefined; + const options: OptionList = undefined; expect(isOptionsModifiable(optionListIds, optionsId, options)).toEqual(false); }); }); @@ -241,13 +241,13 @@ describe('optionsUtils', () => { describe('isInitialOptionsSet', () => { it('should return true if previousOptions is false and currentOptions is truthy', () => { const previousOptions = undefined; - const currentOptions: OptionsList = []; + const currentOptions: OptionList = []; expect(isInitialOptionsSet(previousOptions, currentOptions)).toEqual(true); }); it('should return false if previousOptions is truthy', () => { const previousOptions = []; - const currentOptions: OptionsList = [{ value: 'value', label: 'label' }]; + const currentOptions: OptionList = [{ value: 'value', label: 'label' }]; expect(isInitialOptionsSet(previousOptions, currentOptions)).toEqual(false); }); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/utils/optionsUtils.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/utils/optionsUtils.ts index e196aa08233..594edf59d39 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/utils/optionsUtils.ts +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/utils/optionsUtils.ts @@ -1,5 +1,5 @@ import { SelectedOptionsType } from '../../../../../../components/config/editModal/EditOptions/EditOptions'; -import type { OptionsList } from 'app-shared/types/api/OptionsLists'; +import type { OptionList } from 'app-shared/types/OptionList'; import type { FormItem } from '../../../../../../types/FormItem'; import type { FormComponent, SelectionComponentType } from '../../../../../../types/FormComponent'; import type { FormContainer } from '../../../../../../types/FormContainer'; @@ -18,7 +18,7 @@ export const componentUsesDynamicCodeList = ( export function getSelectedOptionsType( codeListId: string | undefined, - options: OptionsList | undefined, + options: OptionList | undefined, optionListIds: string[] = [], ): SelectedOptionsType { /** It is not permitted for a component to have both options and optionsId set on the same component. */ @@ -34,7 +34,7 @@ export function getSelectedOptionsType( // Todo: Remove once featureFlag "optionListEditor" is removed. export function getSelectedOptionsTypeWithManualSupport( codeListId: string | undefined, - options: OptionsList | undefined, + options: OptionList | undefined, optionListIds: string[] = [], ): SelectedOptionsType { /** It is not permitted for a component to have both options and optionsId set on the same component. */ @@ -51,7 +51,7 @@ export function getSelectedOptionsTypeWithManualSupport( : SelectedOptionsType.CodeList; } -export function hasOptionListChanged(oldOptions: OptionsList, newOptions: OptionsList): boolean { +export function hasOptionListChanged(oldOptions: OptionList, newOptions: OptionList): boolean { return JSON.stringify(oldOptions) !== JSON.stringify(newOptions); } @@ -87,7 +87,7 @@ export function updateComponentOptionsId( export function updateComponentOptions( component: FormItem<SelectionComponentType>, - options: OptionsList, + options: OptionList, ): FormItem<SelectionComponentType> { let newComponent: FormItem<SelectionComponentType> = ObjectUtils.deepCopy(component); @@ -120,7 +120,7 @@ export function isOptionsIdReferenceId( export function isOptionsModifiable( optionListIds: string[], optionsId: undefined | string, - options: undefined | OptionsList, + options: undefined | OptionList, ): boolean { return (!!optionsId && isOptionsIdFromLibrary(optionListIds, optionsId)) || !!options; } @@ -130,8 +130,8 @@ function isOptionsIdFromLibrary(optionListIds: string[], optionsId: undefined | } export function isInitialOptionsSet( - previousOptions: OptionsList, - currentOptions: OptionsList, + previousOptions: OptionList, + currentOptions: OptionList, ): boolean { return !previousOptions && !!currentOptions; } diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditStringValue.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditStringValue.tsx index 066d8572caf..3421a759e6c 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditStringValue.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditStringValue.tsx @@ -6,12 +6,12 @@ import { Combobox, Textfield } from '@digdir/designsystemet-react'; import { useComponentPropertyLabel } from '../../../hooks/useComponentPropertyLabel'; import { useComponentPropertyEnumValue } from '@altinn/ux-editor/hooks/useComponentPropertyEnumValue'; import { StudioNativeSelect } from '@studio/components'; +import { useComponentPropertyHelpText } from '../../../hooks'; const NO_VALUE_SELECTED_IN_NATIVE_SELECT: string = 'NO_VALUE'; export interface EditStringValueProps extends IGenericEditComponent { propertyKey: string; - helpText?: string; enumValues?: string[]; multiple?: boolean; } @@ -20,13 +20,13 @@ export const EditStringValue = ({ component, handleComponentChange, propertyKey, - helpText, enumValues, multiple, }: EditStringValueProps): ReactElement => { const { t } = useTranslation(); const componentPropertyLabel = useComponentPropertyLabel(); const componentEnumValue = useComponentPropertyEnumValue(); + const componentPropertyHelpText = useComponentPropertyHelpText(); const handleValueChange = (newValue): void => { handleComponentChange({ @@ -42,7 +42,7 @@ export const EditStringValue = ({ value={component[propertyKey]} onChange={handleValueChange} propertyPath={`${component.propertyPath}/properties/${propertyKey}`} - helpText={helpText} + helpText={componentPropertyHelpText(propertyKey)} customValidationMessages={(errorCode: string) => { if (errorCode === 'pattern') { return t('validation_errors.pattern'); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.module.css index 329b459f623..7a1932d05fa 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.module.css +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.module.css @@ -1,3 +1,3 @@ .texts { - margin-bottom: var(--fds-spacing-7); + margin-bottom: var(--fds-spacing-3); } diff --git a/frontend/packages/ux-editor/src/hooks/useValidDataModels.test.ts b/frontend/packages/ux-editor/src/hooks/useValidDataModels.test.ts index 1cb572df92e..dfc8274b20f 100644 --- a/frontend/packages/ux-editor/src/hooks/useValidDataModels.test.ts +++ b/frontend/packages/ux-editor/src/hooks/useValidDataModels.test.ts @@ -54,18 +54,4 @@ describe('useValidDataModels', () => { expect(isDataModelValid).toBe(true); expect(selectedDataModel).toEqual(defaultDataModel); }); - - it('should return the default data model from metadata when the current selected data model no longer exists', async () => { - const { result } = setupUseValidDataModelsHook('invalidModel'); - - expect(result.current.isLoadingDataModels).toBe(true); - - await waitFor(() => { - expect(result.current.isLoadingDataModels).toBe(false); - }); - - const { selectedDataModel, isDataModelValid } = result.current; - expect(isDataModelValid).toBe(false); - expect(selectedDataModel).toEqual(defaultDataModel); - }); }); diff --git a/frontend/packages/ux-editor/src/hooks/useValidDataModels.ts b/frontend/packages/ux-editor/src/hooks/useValidDataModels.ts index 5d76888350f..f191d48c38f 100644 --- a/frontend/packages/ux-editor/src/hooks/useValidDataModels.ts +++ b/frontend/packages/ux-editor/src/hooks/useValidDataModels.ts @@ -35,7 +35,7 @@ export const useValidDataModels = (currentDataModel: string) => { return { dataModels, - selectedDataModel: getDataModel(isDataModelValid, dataModelMetadata, currentDataModel), + selectedDataModel: getDataModel(isDataModelValid, dataModel, currentDataModel), dataModelMetadata, isLoadingDataModels: isPendingDataModels || isPendingDataModelMetadata, isDataModelValid, diff --git a/frontend/packages/ux-editor/src/utils/component.ts b/frontend/packages/ux-editor/src/utils/component.ts index 2903ad55525..d7d7a35b1a3 100644 --- a/frontend/packages/ux-editor/src/utils/component.ts +++ b/frontend/packages/ux-editor/src/utils/component.ts @@ -141,10 +141,9 @@ export const generateFormItem = <T extends ComponentType | CustomComponentType>( type: T, id: string, ): FormItem<T> => { - const { defaultProperties, itemType } = formItemConfigs[type]; - const componentType = formItemConfigs[type].componentRef - ? formItemConfigs[type].componentRef - : type; + const { defaultProperties, itemType, componentRef } = formItemConfigs[type]; + const componentType = componentRef ? componentRef : type; + return { ...defaultProperties, id, type: componentType, itemType } as FormItem<T>; }; diff --git a/frontend/packages/ux-editor/src/utils/dataModelUtils.test.ts b/frontend/packages/ux-editor/src/utils/dataModelUtils.test.ts index f8b4b5aecff..754c721774f 100644 --- a/frontend/packages/ux-editor/src/utils/dataModelUtils.test.ts +++ b/frontend/packages/ux-editor/src/utils/dataModelUtils.test.ts @@ -249,45 +249,40 @@ describe('getDataModel', () => { it('should return default data model when it is defined but invalid', () => { const isDataModelValid = false; const currentDataModel = 'currentDataModel'; - const dataModelMetadata = dataModelMetadataMock; - const dataModel = getDataModel(isDataModelValid, dataModelMetadata, currentDataModel); + const dataModel = getDataModel(isDataModelValid, defaultModel, currentDataModel); expect(dataModel).toEqual(defaultModel); }); it('should return default data model when it is undefined and invalid', () => { const isDataModelValid = false; const currentDataModel = undefined; - const dataModelMetadata = dataModelMetadataMock; - const dataModel = getDataModel(isDataModelValid, dataModelMetadata, currentDataModel); + const dataModel = getDataModel(isDataModelValid, defaultModel, currentDataModel); expect(dataModel).toEqual(defaultModel); }); it('should return current data model when it is defined and valid', () => { const isDataModelValid = true; const currentDataModel = 'currentDataModel'; - const dataModelMetadata = dataModelMetadataMock; - const dataModel = getDataModel(isDataModelValid, dataModelMetadata, currentDataModel); + const dataModel = getDataModel(isDataModelValid, defaultModel, currentDataModel); expect(dataModel).toEqual(currentDataModel); }); it('should return current data model if metadata is undefined', () => { const isDataModelValid = true; const currentDataModel = 'currentDataModel'; - const dataModelMetadata = undefined; - const dataModel = getDataModel(isDataModelValid, dataModelMetadata, currentDataModel); + const dataModel = getDataModel(isDataModelValid, defaultModel, currentDataModel); expect(dataModel).toEqual(currentDataModel); }); it('should return default data model if current data model is empty string', () => { const isDataModelValid = true; const currentDataModel = ''; - const dataModelMetadata = dataModelMetadataMock; - const dataModel = getDataModel(isDataModelValid, dataModelMetadata, currentDataModel); + const dataModel = getDataModel(isDataModelValid, defaultModel, currentDataModel); expect(dataModel).toEqual(defaultModel); }); }); diff --git a/frontend/packages/ux-editor/src/utils/dataModelUtils.ts b/frontend/packages/ux-editor/src/utils/dataModelUtils.ts index 9349212e4d2..d1d22e9390f 100644 --- a/frontend/packages/ux-editor/src/utils/dataModelUtils.ts +++ b/frontend/packages/ux-editor/src/utils/dataModelUtils.ts @@ -141,13 +141,13 @@ export const validateSelectedDataField = ( export const getDataModel = ( isDataModelValid: boolean, - dataModelMetadata?: DataModelFieldElement[], + defaultDataModelName: string | undefined, currentDataModel?: string, ): string => { - if (dataModelMetadata) { + if (defaultDataModelName) { return isDataModelValid && currentDataModel !== undefined && currentDataModel !== '' ? currentDataModel - : dataModelMetadata[0]?.id; + : defaultDataModelName; } return currentDataModel; }; diff --git a/frontend/packages/ux-editor/src/utils/designViewUtils/designViewUtils.test.ts b/frontend/packages/ux-editor/src/utils/designViewUtils/designViewUtils.test.ts index 139677b8861..e59e115114a 100644 --- a/frontend/packages/ux-editor/src/utils/designViewUtils/designViewUtils.test.ts +++ b/frontend/packages/ux-editor/src/utils/designViewUtils/designViewUtils.test.ts @@ -4,7 +4,7 @@ const mockNewNameCandidateCorrect: string = 'newPage'; const mockNewNameCandidateExists: string = 'page2'; const mockNewNameCandidateEmpty: string = ''; const mockNewNameCandidateTooLong: string = 'ThisStringIsTooooooooooooooLong'; -const mockNewNameCandidateIllegal: string = 'Page????'; +const mockNewNameCandidateInvalid: string = 'Page????'; const mockNewNameCandidateWithPeriod: string = 'Page.2'; const mockOldName: string = 'oldName'; @@ -42,31 +42,31 @@ describe('designViewUtils', () => { expect(nameErrorkey).toEqual('ux_editor.pages_error_empty'); }); - it('returns length error key when name is too long', () => { + it('returns name invalid error key when name is too long', () => { const nameErrorkey = getPageNameErrorKey( mockNewNameCandidateTooLong, mockOldName, mockLayoutOrder, ); - expect(nameErrorkey).toEqual('ux_editor.pages_error_length'); + expect(nameErrorkey).toEqual('validation_errors.name_invalid'); }); - it('returns formate error when name contains period (.)', () => { + it('returns name invalid error key when name contains period (.)', () => { const nameErrorkey = getPageNameErrorKey( mockNewNameCandidateWithPeriod, mockOldName, mockLayoutOrder, ); - expect(nameErrorkey).toEqual('ux_editor.pages_error_invalid_format'); + expect(nameErrorkey).toEqual('validation_errors.name_invalid'); }); - it('returns format error key when name contains illegal characters', () => { + it('returns name invalid error key when name contains invalid characters', () => { const nameErrorkey = getPageNameErrorKey( - mockNewNameCandidateIllegal, + mockNewNameCandidateInvalid, mockOldName, mockLayoutOrder, ); - expect(nameErrorkey).toEqual('validation_errors.file_name_invalid'); + expect(nameErrorkey).toEqual('validation_errors.name_invalid'); }); it('returns null when oldname and new name is the same', () => { diff --git a/frontend/packages/ux-editor/src/utils/designViewUtils/designViewUtils.ts b/frontend/packages/ux-editor/src/utils/designViewUtils/designViewUtils.ts index 78e802fbe3c..478a23a988b 100644 --- a/frontend/packages/ux-editor/src/utils/designViewUtils/designViewUtils.ts +++ b/frontend/packages/ux-editor/src/utils/designViewUtils/designViewUtils.ts @@ -25,12 +25,8 @@ export const getPageNameErrorKey = ( return 'ux_editor.pages_error_unique'; } else if (!newNameCandidate) { return 'ux_editor.pages_error_empty'; - } else if (newNameCandidate.includes('.')) { - return 'ux_editor.pages_error_invalid_format'; - } else if (newNameCandidate.length > 30) { - return 'ux_editor.pages_error_length'; } else if (!validateLayoutNameAndLayoutSetName(newNameCandidate)) { - return 'validation_errors.file_name_invalid'; + return 'validation_errors.name_invalid'; } else { return null; } diff --git a/frontend/packages/ux-editor/src/utils/exportUtils.test.ts b/frontend/packages/ux-editor/src/utils/exportUtils.test.ts index 521582460ad..1232e32f74b 100644 --- a/frontend/packages/ux-editor/src/utils/exportUtils.test.ts +++ b/frontend/packages/ux-editor/src/utils/exportUtils.test.ts @@ -5,7 +5,7 @@ import type { ITextResources } from 'app-shared/types/global'; import type { ExportForm } from '../types/ExportForm'; import type { FormComponent } from '../types/FormComponent'; import type { FormContainer } from '../types/FormContainer'; -import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; +import type { OptionListData } from 'app-shared/types/OptionList'; describe('generateExportFormFormat', () => { const settings = { @@ -102,7 +102,7 @@ describe('generateExportFormFormat', () => { }, ], }; - const optionListsData: OptionsListsResponse = [ + const optionListsData: OptionListData[] = [ { title: 'optionList1', data: [{ label: 'option1', value: 'option1' }] }, ]; diff --git a/frontend/packages/ux-editor/src/utils/exportUtils.ts b/frontend/packages/ux-editor/src/utils/exportUtils.ts index 07e99122233..05359e5cd1a 100644 --- a/frontend/packages/ux-editor/src/utils/exportUtils.ts +++ b/frontend/packages/ux-editor/src/utils/exportUtils.ts @@ -10,7 +10,7 @@ import type { import type { IFormLayouts, IOption, ITextResourceBindings } from '../types/global'; import { internalLayoutToExternal } from '../converters/formLayoutConverters'; import type { ExternalComponent, ExternalFormLayout } from 'app-shared/types/api'; -import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists'; +import type { OptionListData } from 'app-shared/types/OptionList'; export class ExportUtils { private readonly pageOrder: string[]; @@ -18,7 +18,7 @@ export class ExportUtils { private readonly layoutSetName: string; private readonly appId: string; private readonly textResources: ITextResources; - private readonly optionListsData: OptionsListsResponse; + private readonly optionListDataList: OptionListData[]; private readonly defaultLanguage: string; private readonly includeRestProperties: boolean; @@ -28,7 +28,7 @@ export class ExportUtils { layoutSetName: string, appId: string, textResources: ITextResources, - optionListsData: OptionsListsResponse, + optionListsData: OptionListData[], defaultLanguage: string, includeRestProperties: boolean = false, ) { @@ -37,7 +37,7 @@ export class ExportUtils { this.layoutSetName = layoutSetName; this.appId = appId; this.textResources = textResources; - this.optionListsData = optionListsData; + this.optionListDataList = optionListsData; this.defaultLanguage = defaultLanguage; this.includeRestProperties = includeRestProperties; } @@ -48,7 +48,7 @@ export class ExportUtils { formId: this.layoutSetName, pages: [], }; - this.pageOrder.forEach((layoutName: string, index: number) => { + this.pageOrder?.forEach((layoutName: string, index: number) => { exportForm.pages.push(this.generateLayoutExportFormat(layoutName, index)); }); return exportForm; @@ -141,7 +141,7 @@ export class ExportUtils { return component.options; } if (component.optionsId) { - const optionListData = this.optionListsData.find( + const optionListData = this.optionListDataList.find( (optionListData) => optionListData.title === component.optionsId, ); return optionListData.data; diff --git a/frontend/resourceadm/package.json b/frontend/resourceadm/package.json index 5d4ad9e634e..3e3d1ece7dc 100644 --- a/frontend/resourceadm/package.json +++ b/frontend/resourceadm/package.json @@ -16,7 +16,7 @@ "@svgr/webpack": "8.1.0", "cross-env": "7.0.3", "jest": "29.7.0", - "typescript": "5.7.2", + "typescript": "5.7.3", "webpack": "5.97.1", "webpack-dev-server": "5.2.0" }, diff --git a/frontend/scripts/package.json b/frontend/scripts/package.json index 01abf2684c6..8acfd0ca445 100644 --- a/frontend/scripts/package.json +++ b/frontend/scripts/package.json @@ -8,14 +8,14 @@ "@typescript-eslint/parser": "7.18.0", "eslint": "8.57.1", "eslint-plugin-import": "2.31.0", - "glob": "11.0.0", + "glob": "11.0.1", "husky": "9.1.7", "jsonpointer": "5.0.1", - "lint-staged": "15.3.0", + "lint-staged": "15.4.1", "prettier": "^3.0.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", - "typescript": "5.7.2" + "typescript": "5.7.3" }, "packageManager": "yarn@4.6.0", "private": true, diff --git a/frontend/scripts/yarn.lock b/frontend/scripts/yarn.lock index e5bd99e3718..e129b5882eb 100644 --- a/frontend/scripts/yarn.lock +++ b/frontend/scripts/yarn.lock @@ -373,14 +373,14 @@ __metadata: axios: "npm:1.7.9" eslint: "npm:8.57.1" eslint-plugin-import: "npm:2.31.0" - glob: "npm:11.0.0" + glob: "npm:11.0.1" husky: "npm:9.1.7" jsonpointer: "npm:5.0.1" - lint-staged: "npm:15.3.0" + lint-staged: "npm:15.4.1" prettier: "npm:^3.0.3" ts-node: "npm:^10.9.1" tsconfig-paths: "npm:^4.2.0" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" languageName: unknown linkType: soft @@ -1546,9 +1546,9 @@ __metadata: languageName: node linkType: hard -"glob@npm:11.0.0": - version: 11.0.0 - resolution: "glob@npm:11.0.0" +"glob@npm:11.0.1": + version: 11.0.1 + resolution: "glob@npm:11.0.1" dependencies: foreground-child: "npm:^3.1.0" jackspeak: "npm:^4.0.1" @@ -1558,7 +1558,7 @@ __metadata: path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10/e66939201d11ae30fe97e3364ac2be5c59d6c9bfce18ac633edfad473eb6b46a7553f6f73658f67caaf6cccc1df1ae336298a45e9021fa5695fd78754cc1603e + checksum: 10/57b12a05cc25f1c38f3b24cf6ea7a8bacef11e782c4b9a8c5b0bef3e6c5bcb8c4548cb31eb4115592e0490a024c1bde7359c470565608dd061d3b21179740457 languageName: node linkType: hard @@ -2150,9 +2150,9 @@ __metadata: languageName: node linkType: hard -"lint-staged@npm:15.3.0": - version: 15.3.0 - resolution: "lint-staged@npm:15.3.0" +"lint-staged@npm:15.4.1": + version: 15.4.1 + resolution: "lint-staged@npm:15.4.1" dependencies: chalk: "npm:~5.4.1" commander: "npm:~12.1.0" @@ -2166,7 +2166,7 @@ __metadata: yaml: "npm:~2.6.1" bin: lint-staged: bin/lint-staged.js - checksum: 10/b19ce450641f6cc76be8399658423f0dfa9f9a471aaa427c10bef6a1de2017f1c2547e293de908a57b9202ee20a19fd2305aec3e435cb1d4cfc1d03ace843e9f + checksum: 10/615a1f0a66c6cb35fda928745fec9864498853d5aab49785840b12643e40c6518daf01218b5255a727d32ef9a3738e3766103679cfdcd6f1b320e272920f3b68 languageName: node linkType: hard @@ -3269,23 +3269,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.7.2": - version: 5.7.2 - resolution: "typescript@npm:5.7.2" +"typescript@npm:5.7.3": + version: 5.7.3 + resolution: "typescript@npm:5.7.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/4caa3904df69db9d4a8bedc31bafc1e19ffb7b24fbde2997a1633ae1398d0de5bdbf8daf602ccf3b23faddf1aeeb9b795223a2ed9c9a4fdcaf07bfde114a401a + checksum: 10/6a7e556de91db3d34dc51cd2600e8e91f4c312acd8e52792f243c7818dfadb27bae677175fad6947f9c81efb6c57eb6b2d0c736f196a6ee2f1f7d57b74fc92fa languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.7.2#optional!builtin<compat/typescript>": - version: 5.7.2 - resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin<compat/typescript>::version=5.7.2&hash=5786d5" +"typescript@patch:typescript@npm%3A5.7.3#optional!builtin<compat/typescript>": + version: 5.7.3 + resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin<compat/typescript>::version=5.7.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/d75ca10141afc64fd3474b41a8b082b640555bed388d237558aed64e5827ddadb48f90932c7f4205883f18f5bcab8b6a739a2cfac95855604b0dfeb34bc2f3eb + checksum: 10/dc58d777eb4c01973f7fbf1fd808aad49a0efdf545528dab9b07d94fdcb65b8751742804c3057e9619a4627f2d9cc85547fdd49d9f4326992ad0181b49e61d81 languageName: node linkType: hard diff --git a/frontend/studio-root/components/ContactSection/ContactSection.tsx b/frontend/studio-root/components/ContactSection/ContactSection.tsx index 64b1d882402..3d63de43ae5 100644 --- a/frontend/studio-root/components/ContactSection/ContactSection.tsx +++ b/frontend/studio-root/components/ContactSection/ContactSection.tsx @@ -6,7 +6,7 @@ import classes from './ContactSection.module.css'; export type ContactSectionProps = { title: string; description: string; - link: { + link?: { name: string; href: string; }; @@ -31,7 +31,7 @@ export const ContactSection = ({ </StudioHeading> <StudioParagraph spacing>{description}</StudioParagraph> {additionalContent && <span>{additionalContent}</span>} - <StudioLink href={link.href}>{link.name}</StudioLink> + {link && <StudioLink href={link.href}>{link.name}</StudioLink>} </div> </section> ); diff --git a/frontend/studio-root/components/ContactServiceDesk/ContactServiceDesk.tsx b/frontend/studio-root/components/ContactServiceDesk/ContactServiceDesk.tsx new file mode 100644 index 00000000000..1a20ec1c2dd --- /dev/null +++ b/frontend/studio-root/components/ContactServiceDesk/ContactServiceDesk.tsx @@ -0,0 +1,48 @@ +import React, { type ReactElement } from 'react'; +import { GetInTouchWith } from 'app-shared/getInTouch'; +import { EmailContactProvider } from 'app-shared/getInTouch/providers'; +import { StudioList, StudioLink } from '@studio/components'; +import { Trans } from 'react-i18next'; +import { PhoneContactProvider } from 'app-shared/getInTouch/providers/PhoneContactProvider'; + +export const ContactServiceDesk = (): ReactElement => { + const contactByEmail = new GetInTouchWith(new EmailContactProvider()); + const contactByPhone = new GetInTouchWith(new PhoneContactProvider()); + return ( + <StudioList.Root> + <StudioList.Unordered> + <StudioList.Item> + <Trans + i18nKey='contact.serviceDesk.phone' + components={{ + b: <b />, + a: <StudioLink href={contactByPhone.url('phone')}>{null}</StudioLink>, + }} + /> + </StudioList.Item> + + <StudioList.Item> + <Trans + i18nKey='contact.serviceDesk.emergencyPhone' + values={{ phoneNumber: contactByPhone.url('phone') }} + components={{ + b: <b />, + a: <StudioLink href={contactByPhone.url('emergencyPhone')}>{null}</StudioLink>, + }} + /> + </StudioList.Item> + + <StudioList.Item> + <Trans + i18nKey='contact.serviceDesk.email' + values={{ phoneNumber: contactByPhone.url('phone') }} + components={{ + b: <b />, + a: <StudioLink href={contactByEmail.url('serviceOwner')}>{null}</StudioLink>, + }} + /> + </StudioList.Item> + </StudioList.Unordered> + </StudioList.Root> + ); +}; diff --git a/frontend/studio-root/components/ContactServiceDesk/index.ts b/frontend/studio-root/components/ContactServiceDesk/index.ts new file mode 100644 index 00000000000..2cbb79dacd0 --- /dev/null +++ b/frontend/studio-root/components/ContactServiceDesk/index.ts @@ -0,0 +1 @@ +export { ContactServiceDesk } from './ContactServiceDesk'; diff --git a/frontend/studio-root/pages/Contact/ContactPage.test.tsx b/frontend/studio-root/pages/Contact/ContactPage.test.tsx index 7b4be059c57..1523e710473 100644 --- a/frontend/studio-root/pages/Contact/ContactPage.test.tsx +++ b/frontend/studio-root/pages/Contact/ContactPage.test.tsx @@ -2,6 +2,13 @@ import React from 'react'; import { screen, render } from '@testing-library/react'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { ContactPage } from './ContactPage'; +import { useFetchBelongsToOrgQuery } from '../hooks/queries/useFetchBelongsToOrgQuery'; + +jest.mock('../hooks/queries/useFetchBelongsToOrgQuery'); + +(useFetchBelongsToOrgQuery as jest.Mock).mockReturnValue({ + data: { belongsToOrg: false }, +}); describe('ContactPage', () => { it('should display the main heading', () => { @@ -44,4 +51,29 @@ describe('ContactPage', () => { screen.getByRole('link', { name: textMock('contact.github_issue.link_label') }), ).toBeInTheDocument(); }); + + it('should not render contact info for "Altinn Servicedesk" if the user does not belong to a org', () => { + (useFetchBelongsToOrgQuery as jest.Mock).mockReturnValue({ + data: { belongsToOrg: false }, + }); + render(<ContactPage />); + + expect( + screen.queryByRole('heading', { name: textMock('contact.altinn_servicedesk.heading') }), + ).not.toBeInTheDocument(); + expect( + screen.queryByText(textMock('contact.altinn_servicedesk.content')), + ).not.toBeInTheDocument(); + }); + + it('should display contact information to "Altinn Servicedesk" if user belongs to an org', () => { + (useFetchBelongsToOrgQuery as jest.Mock).mockReturnValue({ + data: { belongsToOrg: true }, + }); + render(<ContactPage />); + expect( + screen.getByRole('heading', { name: textMock('contact.altinn_servicedesk.heading') }), + ).toBeInTheDocument(); + expect(screen.getByText(textMock('contact.altinn_servicedesk.content'))).toBeInTheDocument(); + }); }); diff --git a/frontend/studio-root/pages/Contact/ContactPage.tsx b/frontend/studio-root/pages/Contact/ContactPage.tsx index 7aa6d6e8f8d..406f4fd69f3 100644 --- a/frontend/studio-root/pages/Contact/ContactPage.tsx +++ b/frontend/studio-root/pages/Contact/ContactPage.tsx @@ -1,7 +1,7 @@ import React from 'react'; import classes from './ContactPage.module.css'; import { Trans, useTranslation } from 'react-i18next'; -import { EnvelopeClosedIcon, SlackIcon, GitHubIcon } from '@studio/icons'; +import { EnvelopeClosedIcon, SlackIcon, GitHubIcon, PersonHeadsetIcon } from '@studio/icons'; import { GetInTouchWith } from 'app-shared/getInTouch'; import { EmailContactProvider, @@ -14,6 +14,12 @@ import { StudioParagraph, } from '@studio/components'; import { ContactSection, type ContactSectionProps } from '../../components/ContactSection'; +import { ContactServiceDesk } from '../../components/ContactServiceDesk'; +import { useFetchBelongsToOrgQuery } from '../hooks/queries/useFetchBelongsToOrgQuery'; + +type ContactSectionMetadata = { + shouldHideSection?: boolean; +}; export const ContactPage = (): React.ReactElement => { const { t } = useTranslation(); @@ -21,7 +27,9 @@ export const ContactPage = (): React.ReactElement => { const contactBySlack = new GetInTouchWith(new SlackContactProvider()); const contactByGitHubIssue = new GetInTouchWith(new GitHubIssueContactProvider()); - const contactSections: Array<ContactSectionProps> = [ + const { data: belongsToOrgData } = useFetchBelongsToOrgQuery(); + + const contactSections: Array<ContactSectionProps & ContactSectionMetadata> = [ { title: t('contact.email.heading'), description: t('contact.email.content'), @@ -58,6 +66,13 @@ export const ContactPage = (): React.ReactElement => { }, Icon: GitHubIcon, }, + { + title: t('contact.altinn_servicedesk.heading'), + additionalContent: <ContactServiceDesk />, + description: t('contact.altinn_servicedesk.content'), + Icon: PersonHeadsetIcon, + shouldHideSection: !belongsToOrgData?.belongsToOrg, + }, ]; return ( @@ -69,7 +84,7 @@ export const ContactPage = (): React.ReactElement => { {t('general.contact')} </StudioHeading> </div> - {contactSections.map((contactSection) => ( + {contactSections.filter(filterHiddenSections).map((contactSection) => ( <ContactSection {...contactSection} key={contactSection.title} /> ))} </div> @@ -77,3 +92,7 @@ export const ContactPage = (): React.ReactElement => { </StudioPageImageBackgroundContainer> ); }; + +function filterHiddenSections(section: ContactSectionProps & ContactSectionMetadata): boolean { + return !section.shouldHideSection; +} diff --git a/frontend/studio-root/pages/hooks/queries/useFetchBelongsToOrgQuery.ts b/frontend/studio-root/pages/hooks/queries/useFetchBelongsToOrgQuery.ts new file mode 100644 index 00000000000..419678942e0 --- /dev/null +++ b/frontend/studio-root/pages/hooks/queries/useFetchBelongsToOrgQuery.ts @@ -0,0 +1,16 @@ +import { useQuery, type UseQueryResult } from '@tanstack/react-query'; +import { useServicesContext } from 'app-shared/contexts/ServicesContext'; +import { QueryKey } from 'app-shared/types/QueryKey'; + +type BelongsToOrg = { + belongsToOrg: boolean; +}; + +export const useFetchBelongsToOrgQuery = (): UseQueryResult<BelongsToOrg> => { + const { fetchBelongsToGiteaOrg } = useServicesContext(); + + return useQuery({ + queryKey: [QueryKey.BelongsToOrg], + queryFn: () => fetchBelongsToGiteaOrg(), + }); +}; diff --git a/frontend/testing/playwright/tests/ui-editor/ui-editor-data-model-binding-and-gitea.spec.ts b/frontend/testing/playwright/tests/ui-editor/ui-editor-data-model-binding-and-gitea.spec.ts index 82867f39665..2867a778b8c 100644 --- a/frontend/testing/playwright/tests/ui-editor/ui-editor-data-model-binding-and-gitea.spec.ts +++ b/frontend/testing/playwright/tests/ui-editor/ui-editor-data-model-binding-and-gitea.spec.ts @@ -153,7 +153,7 @@ test('That it is possible to navigate to data model page and create another data page, testAppName, }): Promise<void> => { - await setupAndVerifyUiEditorPage(page, testAppName, ['multipleDataModelsPerTask']); + await setupAndVerifyUiEditorPage(page, testAppName); const header = new Header(page, { app: testAppName }); const dataModelPage = new DataModelPage(page, { app: testAppName }); @@ -165,9 +165,7 @@ test('That it is possible to navigate back to ui-editor page and add the newly a page, testAppName, }): Promise<void> => { - const uiEditorPage = await setupAndVerifyUiEditorPage(page, testAppName, [ - 'multipleDataModelsPerTask', - ]); + const uiEditorPage = await setupAndVerifyUiEditorPage(page, testAppName); const header = new Header(page, { app: testAppName }); await header.clickOnNavigateToPageInTopMenuHeader('create'); diff --git a/package.json b/package.json index f3ddb1959d2..b8cfe934b29 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "@microsoft/applicationinsights-react-js": "17.3.4", "@microsoft/applicationinsights-web": "3.3.4", "@microsoft/signalr": "8.0.7", - "@tanstack/react-query": "5.62.14", - "@tanstack/react-query-devtools": "5.62.14", + "@tanstack/react-query": "5.64.1", + "@tanstack/react-query-devtools": "5.64.1", "ajv": "8.17.1", "ajv-formats": "3.0.1", "i18next": "23.16.8", @@ -20,17 +20,17 @@ "react-dom": "18.3.1", "react-error-boundary": "4.1.2", "react-i18next": "15.4.0", - "react-router-dom": "6.28.1", + "react-router-dom": "6.28.2", "react-toastify": "10.0.6" }, "devDependencies": { "@svgr/webpack": "8.1.0", - "@swc/core": "1.10.4", + "@swc/core": "1.10.7", "@swc/jest": "0.2.37", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", - "@testing-library/react": "16.1.0", - "@testing-library/user-event": "14.5.2", + "@testing-library/react": "16.2.0", + "@testing-library/user-event": "14.6.0", "@types/css-modules": "1.0.5", "@types/jest": "29.5.14", "@types/react": "18.3.18", @@ -43,22 +43,22 @@ "eslint": "8.57.1", "eslint-plugin-import": "2.31.0", "eslint-plugin-jsx-a11y": "6.10.2", - "eslint-plugin-react": "7.37.3", + "eslint-plugin-react": "7.37.4", "eslint-plugin-react-hooks": "5.1.0", "eslint-plugin-testing-library": "7.1.1", - "glob": "11.0.0", + "glob": "11.0.1", "husky": "9.1.7", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-fail-on-console": "3.3.1", "jest-junit": "16.0.0", - "lint-staged": "15.3.0", + "lint-staged": "15.4.1", "mini-css-extract-plugin": "2.9.2", "prettier": "3.4.2", "source-map-loader": "5.0.0", "swc-loader": "0.2.6", "terser-webpack-plugin": "5.3.11", - "typescript": "5.7.2", + "typescript": "5.7.3", "typescript-plugin-css-modules": "5.1.0", "webpack": "5.97.1", "webpack-cli": "5.1.4", @@ -67,8 +67,8 @@ }, "resolutions": { "@testing-library/dom": "10.4.0", - "@babel/traverse": "7.26.4", - "caniuse-lite": "1.0.30001690" + "@babel/traverse": "7.26.5", + "caniuse-lite": "1.0.30001692" }, "lint-staged": { "*{js,jsx,tsx,ts,css,md}": "prettier -w", diff --git a/src/Altinn.Platform/Altinn.Platform.PDF/pom.xml b/src/Altinn.Platform/Altinn.Platform.PDF/pom.xml index b78b727ddd9..3bb18999dd8 100644 --- a/src/Altinn.Platform/Altinn.Platform.PDF/pom.xml +++ b/src/Altinn.Platform/Altinn.Platform.PDF/pom.xml @@ -78,7 +78,7 @@ <dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> - <version>1.14.2</version> + <version>1.15.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> diff --git a/yarn.lock b/yarn.lock index 5eb30223c6b..4efece59039 100644 --- a/yarn.lock +++ b/yarn.lock @@ -64,7 +64,7 @@ __metadata: peerDependencies: react: 18.3.1 react-dom: 18.3.1 - typescript: 5.7.2 + typescript: 5.7.3 languageName: unknown linkType: soft @@ -77,7 +77,7 @@ __metadata: peerDependencies: react: 18.3.1 react-dom: 18.3.1 - typescript: 5.7.2 + typescript: 5.7.3 languageName: unknown linkType: soft @@ -107,12 +107,12 @@ __metadata: version: 0.0.0-use.local resolution: "@altinn/text-editor@workspace:frontend/packages/text-editor" dependencies: - iso-639-1: "npm:3.1.3" + iso-639-1: "npm:3.1.4" jest: "npm:29.7.0" peerDependencies: react: 18.3.1 react-dom: 18.3.1 - typescript: 5.7.2 + typescript: 5.7.3 languageName: unknown linkType: soft @@ -129,7 +129,7 @@ __metadata: react-redux: "npm:9.2.0" redux: "npm:5.0.1" redux-mock-store: "npm:1.5.5" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" uuid: "npm:10.0.0" peerDependencies: webpack: 5.97.1 @@ -145,7 +145,7 @@ __metadata: jest: "npm:29.7.0" react: "npm:18.3.1" react-dom: "npm:18.3.1" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" uuid: "npm:10.0.0" peerDependencies: webpack: 5.97.1 @@ -311,16 +311,16 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.26.3": - version: 7.26.3 - resolution: "@babel/generator@npm:7.26.3" +"@babel/generator@npm:^7.26.5": + version: 7.26.5 + resolution: "@babel/generator@npm:7.26.5" dependencies: - "@babel/parser": "npm:^7.26.3" - "@babel/types": "npm:^7.26.3" + "@babel/parser": "npm:^7.26.5" + "@babel/types": "npm:^7.26.5" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^3.0.2" - checksum: 10/c1d8710cc1c52af9d8d67f7d8ea775578aa500887b327d2a81e27494764a6ef99e438dd7e14cf7cd3153656492ee27a8362980dc438087c0ca39d4e75532c638 + checksum: 10/aa5f176155431d1fb541ca11a7deddec0fc021f20992ced17dc2f688a0a9584e4ff4280f92e8a39302627345cd325762f70f032764806c579c6fd69432542bcb languageName: node linkType: hard @@ -757,14 +757,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.26.3": - version: 7.26.3 - resolution: "@babel/parser@npm:7.26.3" +"@babel/parser@npm:^7.26.5": + version: 7.26.5 + resolution: "@babel/parser@npm:7.26.5" dependencies: - "@babel/types": "npm:^7.26.3" + "@babel/types": "npm:^7.26.5" bin: parser: ./bin/babel-parser.js - checksum: 10/e7e3814b2dc9ee3ed605d38223471fa7d3a84cbe9474d2b5fa7ac57dc1ddf75577b1fd3a93bf7db8f41f28869bda795cddd80223f980be23623b6434bf4c88a8 + checksum: 10/d92097066e3e26625a485149f54c27899e4d94d7ef2f76d8fc9de2019212e7951940a31c0003f26ccad22e664f89ff51e5d5fe80a11eafaaec2420655010533c languageName: node linkType: hard @@ -1856,18 +1856,18 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:7.26.4": - version: 7.26.4 - resolution: "@babel/traverse@npm:7.26.4" +"@babel/traverse@npm:7.26.5": + version: 7.26.5 + resolution: "@babel/traverse@npm:7.26.5" dependencies: "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.3" - "@babel/parser": "npm:^7.26.3" + "@babel/generator": "npm:^7.26.5" + "@babel/parser": "npm:^7.26.5" "@babel/template": "npm:^7.25.9" - "@babel/types": "npm:^7.26.3" + "@babel/types": "npm:^7.26.5" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: 10/30c81a80d66fc39842814bc2e847f4705d30f3859156f130d90a0334fe1d53aa81eed877320141a528ecbc36448acc0f14f544a7d410fa319d1c3ab63b50b58f + checksum: 10/b0131159450e3cd4208354cdea57e832e1a344fcc284b6ac84d1e13567a10501c4747bfd0ce637d2bd02277526b49372cfca71edd5c825cea74dcc9f52bb9225 languageName: node linkType: hard @@ -1914,13 +1914,13 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.26.3": - version: 7.26.3 - resolution: "@babel/types@npm:7.26.3" +"@babel/types@npm:^7.26.5": + version: 7.26.5 + resolution: "@babel/types@npm:7.26.5" dependencies: "@babel/helper-string-parser": "npm:^7.25.9" "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10/c31d0549630a89abfa11410bf82a318b0c87aa846fbf5f9905e47ba5e2aa44f41cc746442f105d622c519e4dc532d35a8d8080460ff4692f9fc7485fbf3a00eb + checksum: 10/148f6bead7bc39371176ba681873c930087503a8bfd2b0dab5090de32752241806c95f4e87cee8b2976bb0277c6cbc150f16c333fc90269634b711d3711c0f18 languageName: node linkType: hard @@ -1941,9 +1941,9 @@ __metadata: languageName: node linkType: hard -"@chromatic-com/storybook@npm:3.2.3": - version: 3.2.3 - resolution: "@chromatic-com/storybook@npm:3.2.3" +"@chromatic-com/storybook@npm:3.2.4": + version: 3.2.4 + resolution: "@chromatic-com/storybook@npm:3.2.4" dependencies: chromatic: "npm:^11.15.0" filesize: "npm:^10.0.12" @@ -1952,7 +1952,7 @@ __metadata: strip-ansi: "npm:^7.1.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/6aa32ff8a227c5ada0667457a36d5db1946f9d89b8fb64153150445c8b67f26647aa48a6afce21f6dc2cad4905418f37c2fd0b12d26c3a42a36edf45b52efb7b + checksum: 10/2d4c7c66810605c9d4c9eccce1755b8dfa4504f9511b53bc2887f2d25bfc564347a9fad110fdd296e9978dcec7f5351a03547ec544ce5ce7284f52db26ce37fe languageName: node linkType: hard @@ -3608,10 +3608,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.21.0": - version: 1.21.0 - resolution: "@remix-run/router@npm:1.21.0" - checksum: 10/cf0fb69d19c1b79095ff67c59cea89086f3982a9a54c8a993818a60fc76e0ebab5a8db647c1a96a662729fad8e806ddd0a96622adf473f5a9f0b99998b2dbad4 +"@remix-run/router@npm:1.21.1": + version: 1.21.1 + resolution: "@remix-run/router@npm:1.21.1" + checksum: 10/22a3dde5dd4ee131bddb5b589f974d6cb9e8378bda219bcdf592f399b9f10b8431d5f230bea507fc3b7f295998f8379942f5f07f920c087e2ecae65c76494e51 languageName: node linkType: hard @@ -3654,9 +3654,9 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-actions@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-actions@npm:8.4.7" +"@storybook/addon-actions@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-actions@npm:8.5.0" dependencies: "@storybook/global": "npm:^5.0.0" "@types/uuid": "npm:^9.0.1" @@ -3664,158 +3664,158 @@ __metadata: polished: "npm:^4.2.2" uuid: "npm:^9.0.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/a691f172f2899bf97ee2d454948a53f94fde29038b1dfc8b1fd902cf0912f72b02f484f3ab4abd6df52237edbed2a7f430a6b7f1b6ba8ee2be1e357c586466bd + storybook: ^8.5.0 + checksum: 10/aa786d69fb46159efa923ce06d4008b59378d009fbd93975109c440916d0eca2306f01eac03d9c3ff59f4294d4fdd69d3d7ed91a9a2df3d6512c5b6afdf22507 languageName: node linkType: hard -"@storybook/addon-backgrounds@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-backgrounds@npm:8.4.7" +"@storybook/addon-backgrounds@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-backgrounds@npm:8.5.0" dependencies: "@storybook/global": "npm:^5.0.0" memoizerific: "npm:^1.11.3" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/504ecd09fcdd8bd8525233469df386944a7baff7c8aaeb737532987d27d113db4ded72e394cfcb6b00262602e9fd070cce801cffbb157be6242ee56e0491577c + storybook: ^8.5.0 + checksum: 10/6d7c6090d0a7586d99818fbb68bf0c97a9c9cc88df57b8ab9dac7e47860551259d4854d7f12ee1484b1ad0143b6a1d28671fa7ad2a6746d274e4f056966e7d1b languageName: node linkType: hard -"@storybook/addon-controls@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-controls@npm:8.4.7" +"@storybook/addon-controls@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-controls@npm:8.5.0" dependencies: "@storybook/global": "npm:^5.0.0" dequal: "npm:^2.0.2" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/29a0d760622cc09517416a5775d8ae7e937fe90ede9d9739a56cdec4bc52564c0d8de535040ed540df912c1c3c04c6f557bc78f792c8af07da91753972f9a512 + storybook: ^8.5.0 + checksum: 10/ea76c18aa473b8cdb6a239b66f52b9994dd57e546dca177210072e42a26be6fc8089ff60702c6933dfb71bccdba510db5b1a6324abb44b0a1af3e0aa4eadf10c languageName: node linkType: hard -"@storybook/addon-docs@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-docs@npm:8.4.7" +"@storybook/addon-docs@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-docs@npm:8.5.0" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/blocks": "npm:8.4.7" - "@storybook/csf-plugin": "npm:8.4.7" - "@storybook/react-dom-shim": "npm:8.4.7" + "@storybook/blocks": "npm:8.5.0" + "@storybook/csf-plugin": "npm:8.5.0" + "@storybook/react-dom-shim": "npm:8.5.0" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/d09fefeefb462a1b6c368e781f4abbb1dfdf0c58e6f9311bc8a2c320699e9e694153ebf3274f4fc54fb85953eb10ced6de11a848c718ffb38a0f59e1b1717220 + storybook: ^8.5.0 + checksum: 10/a30dcdfad64df2520161c4db2048f3b6aef322ec8cb78f76ae42285bf8abc478ef624296e8734b29e99218acb289085cff97942021c71f0fffb7120aa9a8b9c2 languageName: node linkType: hard -"@storybook/addon-essentials@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-essentials@npm:8.4.7" +"@storybook/addon-essentials@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-essentials@npm:8.5.0" dependencies: - "@storybook/addon-actions": "npm:8.4.7" - "@storybook/addon-backgrounds": "npm:8.4.7" - "@storybook/addon-controls": "npm:8.4.7" - "@storybook/addon-docs": "npm:8.4.7" - "@storybook/addon-highlight": "npm:8.4.7" - "@storybook/addon-measure": "npm:8.4.7" - "@storybook/addon-outline": "npm:8.4.7" - "@storybook/addon-toolbars": "npm:8.4.7" - "@storybook/addon-viewport": "npm:8.4.7" + "@storybook/addon-actions": "npm:8.5.0" + "@storybook/addon-backgrounds": "npm:8.5.0" + "@storybook/addon-controls": "npm:8.5.0" + "@storybook/addon-docs": "npm:8.5.0" + "@storybook/addon-highlight": "npm:8.5.0" + "@storybook/addon-measure": "npm:8.5.0" + "@storybook/addon-outline": "npm:8.5.0" + "@storybook/addon-toolbars": "npm:8.5.0" + "@storybook/addon-viewport": "npm:8.5.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/d8731c18935fbc130beee7236b4e80c1621c6964a4109741512b50f065cd8d322446f8ecd84b4120ad1ce2ea829d0d3b5b764cca19c1bd8b73fc77d04dc13f17 + storybook: ^8.5.0 + checksum: 10/704f626070eeeb52029798eb5a191408fb87e4e284a97b4b17918cb7a945f25d805b2e704978829d5a3b5a4853b78aab068a18bbd5696ce8a80559fe778e0867 languageName: node linkType: hard -"@storybook/addon-highlight@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-highlight@npm:8.4.7" +"@storybook/addon-highlight@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-highlight@npm:8.5.0" dependencies: "@storybook/global": "npm:^5.0.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/2d77ce06eaf69445ed6d7c23a666e67576376d770f8fd33055fd35e33c248c2c78f6333461cb92aa21f45bbf06a1255f1977ec3d349fdef531416fc51da809be + storybook: ^8.5.0 + checksum: 10/fe586724b5049e867b30d233a9c520674548a31602e60fea6163eccd09dfbc99e029ef6303f76c958b73c8fc0590079f1bc79b6ed3e38db3423e71b6a907726a languageName: node linkType: hard -"@storybook/addon-interactions@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-interactions@npm:8.4.7" +"@storybook/addon-interactions@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-interactions@npm:8.5.0" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.4.7" - "@storybook/test": "npm:8.4.7" + "@storybook/instrumenter": "npm:8.5.0" + "@storybook/test": "npm:8.5.0" polished: "npm:^4.2.2" ts-dedent: "npm:^2.2.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/24d5c55eb7f320a002d54cc638a58f196d243b248df7735d68bba21e5b2b4cd0ba0369b78e7b67522ef741516b022e9e627db9a59476e0ea2da153736950d1bc + storybook: ^8.5.0 + checksum: 10/c21c91b116bb8f7d4d3462ddf9c0f6f081a5a2ea14ade500b8787a378519828e6d71ca1f7689be2357a48642dc55aaaac74b98134e4c8a29dc2bd582d9458cda languageName: node linkType: hard -"@storybook/addon-links@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-links@npm:8.4.7" +"@storybook/addon-links@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-links@npm:8.5.0" dependencies: - "@storybook/csf": "npm:^0.1.11" + "@storybook/csf": "npm:0.1.12" "@storybook/global": "npm:^5.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 peerDependenciesMeta: react: optional: true - checksum: 10/3d64225348f1c72dec069551044c7781de03a4775acfefb8ebe2d0c1a6e0171692a1222e15191bccd57b76ca9a995032df14974b7a6271f7a9b283c90bff1a00 + checksum: 10/ac22d9063adc20d9c59ddedd0169c43b3ce17843a52c3df04b2e73cac3138cabda88bfb736f03d76740f6755cb63276fc50b6538c6c9b51d4256dcb2ec23d287 languageName: node linkType: hard -"@storybook/addon-measure@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-measure@npm:8.4.7" +"@storybook/addon-measure@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-measure@npm:8.5.0" dependencies: "@storybook/global": "npm:^5.0.0" tiny-invariant: "npm:^1.3.1" peerDependencies: - storybook: ^8.4.7 - checksum: 10/d7c39c6048add359aa43ae10a65dda738f9b893a1963a9485a5ac0337f2961495fbdcf3e3907c2f19e7fb5380089f16c57a54113ed097cbf915bfe7f8b756ede + storybook: ^8.5.0 + checksum: 10/0aa6c38e3b6e890058d1f3dfeb778d8fffafd171603636bb9eccb81ad72d38591f28ed5c67eb409b42dbabdd6fe217b6b40565448aa36e27380f7db1a250c8cb languageName: node linkType: hard -"@storybook/addon-outline@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-outline@npm:8.4.7" +"@storybook/addon-outline@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-outline@npm:8.5.0" dependencies: "@storybook/global": "npm:^5.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/b213e725b3b150b3346e91206cd62bf348f537bfec999a6ca8c7c3a9f772ae69b0e67c50b29e48aaa3315753459bd66782d571a014cafe131d88e2ec3b68f060 + storybook: ^8.5.0 + checksum: 10/2a55046904ffd041c24b00ef61f072234eef447ab2dd3db87643955ad67fd2bdfa79581dc6356dc4d6d6e1559cef17804ef9552315528ff24909ed0dd8d11fc2 languageName: node linkType: hard -"@storybook/addon-toolbars@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-toolbars@npm:8.4.7" +"@storybook/addon-toolbars@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-toolbars@npm:8.5.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/dff15abb4942a95e89d8d84dfa210388b3fec845e2deee473752f340638348c314b68cb5c052644f3a12b1adba2b3b82dd2dd07a6ac427f6043e26993b81722d + storybook: ^8.5.0 + checksum: 10/f2a9b4c1a2a3e5073de4b07843bcca70a7ca000d1b1691a2eb891ea7bd50396374b854cca275df814606450ec8eeee5fb5f7406a3f552d18fc2efa979561a0e7 languageName: node linkType: hard -"@storybook/addon-viewport@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-viewport@npm:8.4.7" +"@storybook/addon-viewport@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-viewport@npm:8.5.0" dependencies: memoizerific: "npm:^1.11.3" peerDependencies: - storybook: ^8.4.7 - checksum: 10/8eaf261e43d70b6453a4ec93a3b6ace728a13db0cf49c6c2f38ca49ad987f7b9268dccf71de2b2dd15cacb8862c9de86689ce258565e2c6fa21c20690ff5761a + storybook: ^8.5.0 + checksum: 10/bf3a237ba2995bef8e33a6902c8f2694f89038d68d663f918f4d0be3e4b9ce8041d9d43bfd59b678ad46b29bf426f716caf09643bdd6bd80f36493bfd014c649 languageName: node linkType: hard @@ -3829,32 +3829,31 @@ __metadata: languageName: node linkType: hard -"@storybook/blocks@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/blocks@npm:8.4.7" +"@storybook/blocks@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/blocks@npm:8.5.0" dependencies: - "@storybook/csf": "npm:^0.1.11" + "@storybook/csf": "npm:0.1.12" "@storybook/icons": "npm:^1.2.12" ts-dedent: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 peerDependenciesMeta: react: optional: true react-dom: optional: true - checksum: 10/d1b92f08b7a829800b16d7a6c6b540eb9b855ca6b6dd7d87cd9c67d211590e76eb43b03d04685950839e764ac96fb6062872868f204fec91bfc1ec4624dbcd6c + checksum: 10/e9319711a35b3f23004a3fc9a235f36ff5a1ebd824b08b4ac3392eb5d4cb085dd990bfc2a328e78147c2ae0f3561d37421d376338c41c4369d3c0aea232e0504 languageName: node linkType: hard -"@storybook/builder-webpack5@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/builder-webpack5@npm:8.4.7" +"@storybook/builder-webpack5@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/builder-webpack5@npm:8.5.0" dependencies: - "@storybook/core-webpack": "npm:8.4.7" - "@types/node": "npm:^22.0.0" + "@storybook/core-webpack": "npm:8.5.0" "@types/semver": "npm:^7.3.4" browser-assert: "npm:^1.2.1" case-sensitive-paths-webpack-plugin: "npm:^2.4.0" @@ -3879,40 +3878,39 @@ __metadata: webpack-hot-middleware: "npm:^2.25.1" webpack-virtual-modules: "npm:^0.6.0" peerDependencies: - storybook: ^8.4.7 + storybook: ^8.5.0 peerDependenciesMeta: typescript: optional: true - checksum: 10/169d12e25780ec5801c051bc3abc3de12d236327f6ea035cfb6938f59db009e6bea88d4bbf1e13ceecb9fa726abd317a11fde88b3143b1e35608e62775d4761d + checksum: 10/81c2ecb54b3ffe52456f2bbb39ff3a71920bb1c9d0daf0a3e0c5b0a23cbe8b96368133005eca7a963e3d2362f9669a40e8b8912044e7c92d0919b9fce5fdfd7f languageName: node linkType: hard -"@storybook/components@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/components@npm:8.4.7" +"@storybook/components@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/components@npm:8.5.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/e39fb81e8386db4f3f76cbf4f82e50512fed2f65a581951c0b61e00c9834c20cfff7f717e936353275dadfe6a25ffaac5d47151adbe1e3be85e709f8a64f6a15 + checksum: 10/2436cd632134a5e6e1733d2081898d41bd7ff328f30a4173d80a27f27b8e989d2b29ee70df4caae54040122381b376d461d9bbfc372d72e98cfceeaf60d0b724 languageName: node linkType: hard -"@storybook/core-webpack@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/core-webpack@npm:8.4.7" +"@storybook/core-webpack@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/core-webpack@npm:8.5.0" dependencies: - "@types/node": "npm:^22.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.4.7 - checksum: 10/561d28962e201086d9f0d739b377aaa5bdaad9eff0dd78cbb6cc9746b70fa3ad86d223e396f414345d19720807a3084ade16c9f2c634d07ed6b8b3355b96be91 + storybook: ^8.5.0 + checksum: 10/92df521e087836cc14d0ba21c69a23aa21c2cda51134a71c236fcfae47bfce8b7ce36725b79099545543fef6600b797179429d46af121e6c966145e43dad1894 languageName: node linkType: hard -"@storybook/core@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/core@npm:8.4.7" +"@storybook/core@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/core@npm:8.5.0" dependencies: - "@storybook/csf": "npm:^0.1.11" + "@storybook/csf": "npm:0.1.12" better-opn: "npm:^3.0.2" browser-assert: "npm:^1.2.1" esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0" @@ -3928,18 +3926,27 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 10/a0bc9e1ea05ae69a914e508966f27208815de7aa2a4bed010c2c194bbdf397742f83e19ffa2efd98d2c04f08854c9b0b327632f6b0a3a90d2d3dd4c5002f14c5 + checksum: 10/847fe2a647238a9acc60cb4ea803a7153fff2bb959892999be63ada12ff2bc9af2bb522d38b16f0c5c34ea78ae5d964078a797ee7b6d479252b3882061372e83 languageName: node linkType: hard -"@storybook/csf-plugin@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/csf-plugin@npm:8.4.7" +"@storybook/csf-plugin@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/csf-plugin@npm:8.5.0" dependencies: unplugin: "npm:^1.3.1" peerDependencies: - storybook: ^8.4.7 - checksum: 10/d9006d1a506796717528ee81948be89c8ca7e4a4ad463e024936d828b8e91e12940a41f054db4d5b1f1b058146113aaeb415eca87ca94142c3ef1ef501aead17 + storybook: ^8.5.0 + checksum: 10/4d81e94dbd7aea3dd7da36c9ec80559d5c5d3597aae3f3a59472b803fda3eb8a33c35dcc6a4d238f1daf1d39d1f135e0daa26d3d1b29feabf942b6138d3b5054 + languageName: node + linkType: hard + +"@storybook/csf@npm:0.1.12": + version: 0.1.12 + resolution: "@storybook/csf@npm:0.1.12" + dependencies: + type-fest: "npm:^2.19.0" + checksum: 10/f661709de5bd68bfd4ced67df31ef26341168d6679bc13564cb024cfdbc8fdfa94d384267c20b3c858a3058b1ee8dbd71cea169245fcf7b28298890d6c3e1da4 languageName: node linkType: hard @@ -3969,35 +3976,34 @@ __metadata: languageName: node linkType: hard -"@storybook/instrumenter@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/instrumenter@npm:8.4.7" +"@storybook/instrumenter@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/instrumenter@npm:8.5.0" dependencies: "@storybook/global": "npm:^5.0.0" "@vitest/utils": "npm:^2.1.1" peerDependencies: - storybook: ^8.4.7 - checksum: 10/8142789e7dd32f881cf9de551078fb3574cc54b47bb8fd2c8b66ea1fb100f14af702f4cbd4bc11a8d1dd4c89f5d0ce7574d2e232b197c43bbebd0a30c06c7e75 + storybook: ^8.5.0 + checksum: 10/e826f450f1eb9b10be6ca05b8a8abd661f65126fdf0e49fb662e027018cccb4205bc9bbd8957a136307eeafea273642e24b0ff8d2e585f3e2ff00628f16c6e78 languageName: node linkType: hard -"@storybook/manager-api@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/manager-api@npm:8.4.7" +"@storybook/manager-api@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/manager-api@npm:8.5.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/2b826ec55de7ea0b5b5151dfa896f3e7eddfd36ede61f8a7ad14a37733d5d5645565f863dbde7e2272f1e9b5717f26de7802ae60e297a2647ee2c4c072ed3069 + checksum: 10/d0e343579430e1896b6cf5fe5a81d5784a9fcad2725d6cc3012d05f6195c45cbe39bc5fcae21b334dd701cf6ab4d805261f513394687734e3a987906449f18d4 languageName: node linkType: hard -"@storybook/preset-react-webpack@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/preset-react-webpack@npm:8.4.7" +"@storybook/preset-react-webpack@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/preset-react-webpack@npm:8.5.0" dependencies: - "@storybook/core-webpack": "npm:8.4.7" - "@storybook/react": "npm:8.4.7" + "@storybook/core-webpack": "npm:8.5.0" + "@storybook/react": "npm:8.5.0" "@storybook/react-docgen-typescript-plugin": "npm:1.0.6--canary.9.0c3f3b7.0" - "@types/node": "npm:^22.0.0" "@types/semver": "npm:^7.3.4" find-up: "npm:^5.0.0" magic-string: "npm:^0.30.5" @@ -4009,20 +4015,20 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 peerDependenciesMeta: typescript: optional: true - checksum: 10/d338fa45547126ee35ec0433a9811d9c816cebf27ec7598539b62bb08b5a9c39634986670e1cbcf11778a13691ee0695fc71e4dea68c393e5feb6ae478d047f5 + checksum: 10/325f7ea4236ee8685b0f17ace4ef376c709cd10a5dfdb5463974c8062fcea95efff43209002d279067dc0dc262ef8f9aea693299d7f9794800cda6f5ec24f4e2 languageName: node linkType: hard -"@storybook/preview-api@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/preview-api@npm:8.4.7" +"@storybook/preview-api@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/preview-api@npm:8.5.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/1c467bb2c16c5998b9bc4c2c013e6786936d5f6a373ad8d8ab1beb626616c3187329fdfc3a709663b4af963c7e5789a1401166c6e2a3a66a12f66e858aa94e91 + checksum: 10/8e13270e970a36c935eb4ee926cfc8d187f33d55fc121793845c0b6e37eba7e2581b2dba5dea6bffb843d202f61f20aed71e6e9dceae3e5438b017acdb32609a languageName: node linkType: hard @@ -4044,86 +4050,85 @@ __metadata: languageName: node linkType: hard -"@storybook/react-dom-shim@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/react-dom-shim@npm:8.4.7" +"@storybook/react-dom-shim@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/react-dom-shim@npm:8.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 - checksum: 10/c45af3e1320f131231aad794c8f0d565677313ba0edbac31e3551bab371927f31ec780151fbc451c57205bd0b73a157b95901d2c4d06c6a63ce868866948f328 + storybook: ^8.5.0 + checksum: 10/50cb2f5128770e34d50250441881e80b6d43ff452f42442d6d1366569bf4def46a31c9739fde13882581df528f2558b1f53365ca103732782339aa934f0e5a88 languageName: node linkType: hard -"@storybook/react-webpack5@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/react-webpack5@npm:8.4.7" +"@storybook/react-webpack5@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/react-webpack5@npm:8.5.0" dependencies: - "@storybook/builder-webpack5": "npm:8.4.7" - "@storybook/preset-react-webpack": "npm:8.4.7" - "@storybook/react": "npm:8.4.7" - "@types/node": "npm:^22.0.0" + "@storybook/builder-webpack5": "npm:8.5.0" + "@storybook/preset-react-webpack": "npm:8.5.0" + "@storybook/react": "npm:8.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 typescript: ">= 4.2.x" peerDependenciesMeta: typescript: optional: true - checksum: 10/368565a6f8173025dbaa621d161562d0076f87e7ffd72e4bcd5a145501aa6376bc6a6fadad52a9876b586cc1ff82ffd0774faa9fc4833db41447121a8d6bae86 + checksum: 10/bfae95373e10dbceb18a2c7c26e499ec216b69587b44b2fdc477a7d9e54035ab6552c42caed0ca5e28d6fe0b001a63e344979285aee6f2e7c92c134bf45b1a8e languageName: node linkType: hard -"@storybook/react@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/react@npm:8.4.7" +"@storybook/react@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/react@npm:8.5.0" dependencies: - "@storybook/components": "npm:8.4.7" + "@storybook/components": "npm:8.5.0" "@storybook/global": "npm:^5.0.0" - "@storybook/manager-api": "npm:8.4.7" - "@storybook/preview-api": "npm:8.4.7" - "@storybook/react-dom-shim": "npm:8.4.7" - "@storybook/theming": "npm:8.4.7" + "@storybook/manager-api": "npm:8.5.0" + "@storybook/preview-api": "npm:8.5.0" + "@storybook/react-dom-shim": "npm:8.5.0" + "@storybook/theming": "npm:8.5.0" peerDependencies: - "@storybook/test": 8.4.7 + "@storybook/test": 8.5.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 typescript: ">= 4.2.x" peerDependenciesMeta: "@storybook/test": optional: true typescript: optional: true - checksum: 10/4138b11118a313dca2551de307b994f84121c306f2d3a66c29ef9fb07352451a899ce91fd8736149182f8806a7c03dbbe7a4a7d463b0ab3eddbd195057c4cbf8 + checksum: 10/e8ccd36def9719efdc8f2cf2d7bca9fe81229f9e3cee89da1a82f153e868d65fe5423e9f0c2f944903fdb94416677503537031e752eee0ff5a2c4211a8c26e01 languageName: node linkType: hard -"@storybook/test@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/test@npm:8.4.7" +"@storybook/test@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/test@npm:8.5.0" dependencies: - "@storybook/csf": "npm:^0.1.11" + "@storybook/csf": "npm:0.1.12" "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.4.7" + "@storybook/instrumenter": "npm:8.5.0" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.5.0" "@testing-library/user-event": "npm:14.5.2" "@vitest/expect": "npm:2.0.5" "@vitest/spy": "npm:2.0.5" peerDependencies: - storybook: ^8.4.7 - checksum: 10/e6e8c2b5b63337e297362716a9de81818f8d94107cc1eea6c1aef75d0ad93d417d277fa90068ee1960acba98ea2658660514148d106a547419c9088c20905f02 + storybook: ^8.5.0 + checksum: 10/14fd4f8b83efd4b545afcef0f63e2c82c86c015d7cac4fa0c3b5e1ae6e474816ad238ae0a4f7ad5ee1c352d215d23e93f2e7db794bdf6c1b031f63ac07edec07 languageName: node linkType: hard -"@storybook/theming@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/theming@npm:8.4.7" +"@storybook/theming@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/theming@npm:8.5.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/47d29993c33bb29994d227af30e099579b7cf760652ed743020f5d7e5a5974f59a6ebeb1cc8995e6158da9cf768a8d2f559d1d819cc082d0bcdb056d85fdcb29 + checksum: 10/d93b5d104c500b674353b53ba6d5b0ad86e83206e84587a44c2eac561713bccaf53017cbb6d498d540e8eb4a21781e6494a86a0d22246404bad2dc78f1f99df6 languageName: node linkType: hard @@ -4131,19 +4136,19 @@ __metadata: version: 0.0.0-use.local resolution: "@studio/components@workspace:frontend/libs/studio-components" dependencies: - "@chromatic-com/storybook": "npm:3.2.3" - "@storybook/addon-essentials": "npm:8.4.7" - "@storybook/addon-interactions": "npm:8.4.7" - "@storybook/addon-links": "npm:8.4.7" + "@chromatic-com/storybook": "npm:3.2.4" + "@storybook/addon-essentials": "npm:8.5.0" + "@storybook/addon-interactions": "npm:8.5.0" + "@storybook/addon-links": "npm:8.5.0" "@storybook/addon-webpack5-compiler-swc": "npm:2.0.0" - "@storybook/blocks": "npm:8.4.7" - "@storybook/react": "npm:8.4.7" - "@storybook/react-webpack5": "npm:8.4.7" - "@storybook/test": "npm:8.4.7" + "@storybook/blocks": "npm:8.5.0" + "@storybook/react": "npm:8.5.0" + "@storybook/react-webpack5": "npm:8.5.0" + "@storybook/test": "npm:8.5.0" "@studio/icons": "workspace:^" "@studio/pure-functions": "workspace:^" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.1.0" + "@testing-library/react": "npm:16.2.0" "@types/jest": "npm:^29.5.5" ajv: "npm:8.17.1" eslint: "npm:8.57.1" @@ -4156,7 +4161,7 @@ __metadata: react-dom: "npm:^18.2.0" storybook: "npm:^8.0.4" ts-jest: "npm:^29.1.1" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" uuid: "npm:10.0.0" languageName: unknown linkType: soft @@ -4183,7 +4188,7 @@ __metadata: dependencies: "@studio/pure-functions": "workspace:^" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.1.0" + "@testing-library/react": "npm:16.2.0" "@types/jest": "npm:^29.5.5" eslint: "npm:8.57.1" jest: "npm:^29.7.0" @@ -4191,7 +4196,7 @@ __metadata: react: "npm:^18.2.0" react-dom: "npm:^18.2.0" ts-jest: "npm:^29.1.1" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" uuid: "npm:10.0.0" peerDependencies: react-router-dom: ">=6.0.0" @@ -4204,14 +4209,14 @@ __metadata: dependencies: "@navikt/aksel-icons": "npm:^6.0.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.1.0" + "@testing-library/react": "npm:16.2.0" "@types/jest": "npm:^29.5.5" jest: "npm:^29.7.0" jest-environment-jsdom: "npm:^29.7.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" ts-jest: "npm:^29.1.1" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" languageName: unknown linkType: soft @@ -4224,7 +4229,7 @@ __metadata: jest: "npm:^29.7.0" jest-environment-jsdom: "npm:^29.7.0" ts-jest: "npm:^29.1.1" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" languageName: unknown linkType: soft @@ -4384,9 +4389,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-darwin-arm64@npm:1.10.4" +"@swc/core-darwin-arm64@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-darwin-arm64@npm:1.10.7" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -4398,9 +4403,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-darwin-x64@npm:1.10.4" +"@swc/core-darwin-x64@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-darwin-x64@npm:1.10.7" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -4412,9 +4417,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.10.4" +"@swc/core-linux-arm-gnueabihf@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.10.7" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -4426,9 +4431,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-linux-arm64-gnu@npm:1.10.4" +"@swc/core-linux-arm64-gnu@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-linux-arm64-gnu@npm:1.10.7" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard @@ -4440,9 +4445,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-linux-arm64-musl@npm:1.10.4" +"@swc/core-linux-arm64-musl@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-linux-arm64-musl@npm:1.10.7" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard @@ -4454,9 +4459,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-linux-x64-gnu@npm:1.10.4" +"@swc/core-linux-x64-gnu@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-linux-x64-gnu@npm:1.10.7" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard @@ -4468,9 +4473,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-linux-x64-musl@npm:1.10.4" +"@swc/core-linux-x64-musl@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-linux-x64-musl@npm:1.10.7" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard @@ -4482,9 +4487,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-win32-arm64-msvc@npm:1.10.4" +"@swc/core-win32-arm64-msvc@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-win32-arm64-msvc@npm:1.10.7" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -4496,9 +4501,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-win32-ia32-msvc@npm:1.10.4" +"@swc/core-win32-ia32-msvc@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-win32-ia32-msvc@npm:1.10.7" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -4510,9 +4515,9 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core-win32-x64-msvc@npm:1.10.4" +"@swc/core-win32-x64-msvc@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core-win32-x64-msvc@npm:1.10.7" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -4524,20 +4529,20 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:1.10.4": - version: 1.10.4 - resolution: "@swc/core@npm:1.10.4" +"@swc/core@npm:1.10.7": + version: 1.10.7 + resolution: "@swc/core@npm:1.10.7" dependencies: - "@swc/core-darwin-arm64": "npm:1.10.4" - "@swc/core-darwin-x64": "npm:1.10.4" - "@swc/core-linux-arm-gnueabihf": "npm:1.10.4" - "@swc/core-linux-arm64-gnu": "npm:1.10.4" - "@swc/core-linux-arm64-musl": "npm:1.10.4" - "@swc/core-linux-x64-gnu": "npm:1.10.4" - "@swc/core-linux-x64-musl": "npm:1.10.4" - "@swc/core-win32-arm64-msvc": "npm:1.10.4" - "@swc/core-win32-ia32-msvc": "npm:1.10.4" - "@swc/core-win32-x64-msvc": "npm:1.10.4" + "@swc/core-darwin-arm64": "npm:1.10.7" + "@swc/core-darwin-x64": "npm:1.10.7" + "@swc/core-linux-arm-gnueabihf": "npm:1.10.7" + "@swc/core-linux-arm64-gnu": "npm:1.10.7" + "@swc/core-linux-arm64-musl": "npm:1.10.7" + "@swc/core-linux-x64-gnu": "npm:1.10.7" + "@swc/core-linux-x64-musl": "npm:1.10.7" + "@swc/core-win32-arm64-msvc": "npm:1.10.7" + "@swc/core-win32-ia32-msvc": "npm:1.10.7" + "@swc/core-win32-x64-msvc": "npm:1.10.7" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.17" peerDependencies: @@ -4566,7 +4571,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/3b5580b702dcec23912ec2b6472f55887006918865d960a08866e3342139af014d762223ca047bfb6f0277820835ea1f3073936a0e0e3bc86046a7335e22ca98 + checksum: 10/0c1e62c0eaa5750096d869be3f48e6a1346bed8a8736d90c70f841352e26d2618adff18d24a7d2b567fd1cfd96e655249b644f85cf16b115fad5791dc289929f languageName: node linkType: hard @@ -4654,40 +4659,40 @@ __metadata: languageName: node linkType: hard -"@tanstack/query-core@npm:5.62.12": - version: 5.62.12 - resolution: "@tanstack/query-core@npm:5.62.12" - checksum: 10/f1e1d58e509bf11eb20db81e6b20aaf8f22f6f362eb802429ba484bd2a903829e40527028f5ae87cbac61af7eb1eedd24bfceb305939fe2ad344667ad0bde42c +"@tanstack/query-core@npm:5.64.1": + version: 5.64.1 + resolution: "@tanstack/query-core@npm:5.64.1" + checksum: 10/7191e6a1c7f8775baf41376f1bb5effe37e6ad31fe686a3ca2d18c92e6309f9efe8b5f2f0de98dbef7f4741f6f1892e9cf41fb6c3f6c6f7d012b81ebf0d003a0 languageName: node linkType: hard -"@tanstack/query-devtools@npm:5.62.9": - version: 5.62.9 - resolution: "@tanstack/query-devtools@npm:5.62.9" - checksum: 10/33c3617da4db69fa2805dd7cb76ce4ffc769974ca670421d6a5c16f108c80f885d264987f1f3b3b0bb2cf8fdf609e73b2dfc50fe0b735af1bd1cc62e0d4857c7 +"@tanstack/query-devtools@npm:5.62.16": + version: 5.62.16 + resolution: "@tanstack/query-devtools@npm:5.62.16" + checksum: 10/4d42539ca2c73072a0828cf1028f83a2b63b18d2054e5cf38843cc5fad5959e3c68cf84a16bf1fd45c575d86bcaf94b0eaec10bc8aacd89684ebaa106fdc4b46 languageName: node linkType: hard -"@tanstack/react-query-devtools@npm:5.62.14": - version: 5.62.14 - resolution: "@tanstack/react-query-devtools@npm:5.62.14" +"@tanstack/react-query-devtools@npm:5.64.1": + version: 5.64.1 + resolution: "@tanstack/react-query-devtools@npm:5.64.1" dependencies: - "@tanstack/query-devtools": "npm:5.62.9" + "@tanstack/query-devtools": "npm:5.62.16" peerDependencies: - "@tanstack/react-query": ^5.62.14 + "@tanstack/react-query": ^5.64.1 react: ^18 || ^19 - checksum: 10/e35a0a8f4e88ffa13332cca8c1f51e46b5f5a01a46590c926850ec8258d78e0968a60a1c53cfe6a4e96a25f23a9ff0306871c81e411ce0d28e42f1ad88e4df5d + checksum: 10/4540273039bcf3c78cc5fc8c52550d02f3b715cf00bf0308e986b4878553de2039b31343dabd344c08aa1ed4f5c8af64edbd42d6374cfbf678983a9405e99471 languageName: node linkType: hard -"@tanstack/react-query@npm:5.62.14": - version: 5.62.14 - resolution: "@tanstack/react-query@npm:5.62.14" +"@tanstack/react-query@npm:5.64.1": + version: 5.64.1 + resolution: "@tanstack/react-query@npm:5.64.1" dependencies: - "@tanstack/query-core": "npm:5.62.12" + "@tanstack/query-core": "npm:5.64.1" peerDependencies: react: ^18 || ^19 - checksum: 10/156daf0a59accfa168ee0a616627d65a675245d919676d5cce4fc25baba4e9409ddf870effdec14189bb40f1879c3feb93af6ee5bb2ede1bde8b2c9ecc716aa9 + checksum: 10/eaf24079f1ea5f37342232e03c82a799d048ba4d36b66cb9d0de81b2e9d44184c69f65a574c84bba82a77b5566e55ded116913d839e49217c910eb6952e9c5af languageName: node linkType: hard @@ -4768,9 +4773,9 @@ __metadata: languageName: node linkType: hard -"@testing-library/react@npm:16.1.0": - version: 16.1.0 - resolution: "@testing-library/react@npm:16.1.0" +"@testing-library/react@npm:16.2.0": + version: 16.2.0 + resolution: "@testing-library/react@npm:16.2.0" dependencies: "@babel/runtime": "npm:^7.12.5" peerDependencies: @@ -4784,7 +4789,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/2a20e0dbfadbc93d45a84e82281ed47deed54a6a5fc1461a523172d7fbc0481e8502cf98a2080f38aba94290b3d745671a1c9e320e6f76ad6afcca67c580b963 + checksum: 10/cf10bfa9a363384e6861417696fff4a464a64f98ec6f0bb7f1fa7cbb550d075d23a2f6a943b7df85dded7bde3234f6ea6b6e36f95211f4544b846ea72c288289 languageName: node linkType: hard @@ -4797,6 +4802,15 @@ __metadata: languageName: node linkType: hard +"@testing-library/user-event@npm:14.6.0": + version: 14.6.0 + resolution: "@testing-library/user-event@npm:14.6.0" + peerDependencies: + "@testing-library/dom": ">=7.21.4" + checksum: 10/01a7481642ceda10324ff5356e3cfd9c6131b0cecbcbdd5938096d4d3f8ce9e548e9b460ef35bad8f3649dc392c808044a5abd78de8218a4bc21c91125be85df + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -5179,11 +5193,11 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:^22.0.0": - version: 22.10.5 - resolution: "@types/node@npm:22.10.5" + version: 22.10.7 + resolution: "@types/node@npm:22.10.7" dependencies: undici-types: "npm:~6.20.0" - checksum: 10/a5366961ffa9921e8f15435bc18ea9f8b7a7bb6b3d92dd5e93ebcd25e8af65708872bd8e6fee274b4655bab9ca80fbff9f0e42b5b53857790f13cf68cf4cbbfc + checksum: 10/64cde1c2f5e5f7d597d3bd462f52c3c2d688a66623eb75d25e1d1d63d384ef553a27100635ad0dbb7d74da517048aa636947863eb624cf85f25d2f22370ce474 languageName: node linkType: hard @@ -6309,14 +6323,14 @@ __metadata: "@microsoft/applicationinsights-web": "npm:3.3.4" "@microsoft/signalr": "npm:8.0.7" "@svgr/webpack": "npm:8.1.0" - "@swc/core": "npm:1.10.4" + "@swc/core": "npm:1.10.7" "@swc/jest": "npm:0.2.37" - "@tanstack/react-query": "npm:5.62.14" - "@tanstack/react-query-devtools": "npm:5.62.14" + "@tanstack/react-query": "npm:5.64.1" + "@tanstack/react-query-devtools": "npm:5.64.1" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" - "@testing-library/react": "npm:16.1.0" - "@testing-library/user-event": "npm:14.5.2" + "@testing-library/react": "npm:16.2.0" + "@testing-library/user-event": "npm:14.6.0" "@types/css-modules": "npm:1.0.5" "@types/jest": "npm:29.5.14" "@types/react": "npm:18.3.18" @@ -6331,10 +6345,10 @@ __metadata: eslint: "npm:8.57.1" eslint-plugin-import: "npm:2.31.0" eslint-plugin-jsx-a11y: "npm:6.10.2" - eslint-plugin-react: "npm:7.37.3" + eslint-plugin-react: "npm:7.37.4" eslint-plugin-react-hooks: "npm:5.1.0" eslint-plugin-testing-library: "npm:7.1.1" - glob: "npm:11.0.0" + glob: "npm:11.0.1" husky: "npm:9.1.7" i18next: "npm:23.16.8" identity-obj-proxy: "npm:3.0.0" @@ -6342,19 +6356,19 @@ __metadata: jest-environment-jsdom: "npm:29.7.0" jest-fail-on-console: "npm:3.3.1" jest-junit: "npm:16.0.0" - lint-staged: "npm:15.3.0" + lint-staged: "npm:15.4.1" mini-css-extract-plugin: "npm:2.9.2" prettier: "npm:3.4.2" react: "npm:18.3.1" react-dom: "npm:18.3.1" react-error-boundary: "npm:4.1.2" react-i18next: "npm:15.4.0" - react-router-dom: "npm:6.28.1" + react-router-dom: "npm:6.28.2" react-toastify: "npm:10.0.6" source-map-loader: "npm:5.0.0" swc-loader: "npm:0.2.6" terser-webpack-plugin: "npm:5.3.11" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" typescript-plugin-css-modules: "npm:5.1.0" webpack: "npm:5.97.1" webpack-cli: "npm:5.1.4" @@ -6470,8 +6484,8 @@ __metadata: react: "npm:18.3.1" react-dom: "npm:18.3.1" react-i18next: "npm:15.4.0" - react-router-dom: "npm:6.28.1" - typescript: "npm:5.7.2" + react-router-dom: "npm:6.28.2" + typescript: "npm:5.7.3" webpack: "npm:5.97.1" webpack-dev-server: "npm:5.2.0" languageName: unknown @@ -6485,8 +6499,8 @@ __metadata: jest: "npm:29.7.0" react: "npm:18.3.1" react-dom: "npm:18.3.1" - react-router-dom: "npm:6.28.1" - typescript: "npm:5.7.2" + react-router-dom: "npm:6.28.2" + typescript: "npm:5.7.3" webpack: "npm:5.97.1" webpack-dev-server: "npm:5.2.0" languageName: unknown @@ -6499,11 +6513,11 @@ __metadata: "@types/react": "npm:18.3.18" classnames: "npm:2.5.1" jest: "npm:29.7.0" - qs: "npm:6.13.1" + qs: "npm:6.14.0" react: "npm:18.3.1" react-dom: "npm:18.3.1" - react-router-dom: "npm:6.28.1" - typescript: "npm:5.7.2" + react-router-dom: "npm:6.28.2" + typescript: "npm:5.7.3" languageName: unknown linkType: soft @@ -7499,10 +7513,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:1.0.30001690": - version: 1.0.30001690 - resolution: "caniuse-lite@npm:1.0.30001690" - checksum: 10/9fb4659eb09a298601b9593739072c481e2f5cc524bd0530e5e0f002e66246da5e866669854dfc0d53195ee36b201dab02f7933a7cdf60ccba7adb2d4a304caf +"caniuse-lite@npm:1.0.30001692": + version: 1.0.30001692 + resolution: "caniuse-lite@npm:1.0.30001692" + checksum: 10/92449ec9e9ac6cd8ce7ecc18a8759ae34e4b3ef412acd998714ee9d70dc286bc8d0d6e4917fa454798da9b37667eb5b3b41386bc9d25e4274d0b9c7af8339b0e languageName: node linkType: hard @@ -8514,8 +8528,8 @@ __metadata: jest: "npm:29.7.0" react: "npm:18.3.1" react-dom: "npm:18.3.1" - react-router-dom: "npm:6.28.1" - typescript: "npm:5.7.2" + react-router-dom: "npm:6.28.2" + typescript: "npm:5.7.3" webpack: "npm:5.97.1" webpack-dev-server: "npm:5.2.0" languageName: unknown @@ -9996,9 +10010,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react@npm:7.37.3": - version: 7.37.3 - resolution: "eslint-plugin-react@npm:7.37.3" +"eslint-plugin-react@npm:7.37.4": + version: 7.37.4 + resolution: "eslint-plugin-react@npm:7.37.4" dependencies: array-includes: "npm:^3.1.8" array.prototype.findlast: "npm:^1.2.5" @@ -10020,7 +10034,7 @@ __metadata: string.prototype.repeat: "npm:^1.0.0" peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - checksum: 10/30042b95c440a962157192f67369d8c9171f7c73e992695e5674c9d28c3cfe4098488eca86dfa7c433d4c8a91243cbafdc80c7e42d2c1720a427ddb36e65457e + checksum: 10/c538c10665c87cb90a0bcc4efe53a758570db10997d079d31474a9760116ef5584648fa22403d889ca672df8071bda10b40434ea0499e5ee8360bc5c8aba1679 languageName: node linkType: hard @@ -11111,9 +11125,9 @@ __metadata: languageName: node linkType: hard -"glob@npm:11.0.0": - version: 11.0.0 - resolution: "glob@npm:11.0.0" +"glob@npm:11.0.1": + version: 11.0.1 + resolution: "glob@npm:11.0.1" dependencies: foreground-child: "npm:^3.1.0" jackspeak: "npm:^4.0.1" @@ -11123,7 +11137,7 @@ __metadata: path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10/e66939201d11ae30fe97e3364ac2be5c59d6c9bfce18ac633edfad473eb6b46a7553f6f73658f67caaf6cccc1df1ae336298a45e9021fa5695fd78754cc1603e + checksum: 10/57b12a05cc25f1c38f3b24cf6ea7a8bacef11e782c4b9a8c5b0bef3e6c5bcb8c4548cb31eb4115592e0490a024c1bde7359c470565608dd061d3b21179740457 languageName: node linkType: hard @@ -12562,10 +12576,10 @@ __metadata: languageName: node linkType: hard -"iso-639-1@npm:3.1.3": - version: 3.1.3 - resolution: "iso-639-1@npm:3.1.3" - checksum: 10/a1fa0c5770fd6d70782b453c253fc352e260fc335a4a08b16d9cff33de4883c2f52cb5c85a41201412b7a5e5d558def2ca6d5a1fa42830bddf5e8365a8519c6a +"iso-639-1@npm:3.1.4": + version: 3.1.4 + resolution: "iso-639-1@npm:3.1.4" + checksum: 10/864d1b4f5ea636493561fc800c88846882b8a280339a6be009f7f67092f8c193d2e1c150bd1f0a64de28466756694620d89e724b33d6ddf42ad804de3983b0e6 languageName: node linkType: hard @@ -13618,9 +13632,9 @@ __metadata: languageName: node linkType: hard -"lint-staged@npm:15.3.0": - version: 15.3.0 - resolution: "lint-staged@npm:15.3.0" +"lint-staged@npm:15.4.1": + version: 15.4.1 + resolution: "lint-staged@npm:15.4.1" dependencies: chalk: "npm:~5.4.1" commander: "npm:~12.1.0" @@ -13634,7 +13648,7 @@ __metadata: yaml: "npm:~2.6.1" bin: lint-staged: bin/lint-staged.js - checksum: 10/b19ce450641f6cc76be8399658423f0dfa9f9a471aaa427c10bef6a1de2017f1c2547e293de908a57b9202ee20a19fd2305aec3e435cb1d4cfc1d03ace843e9f + checksum: 10/615a1f0a66c6cb35fda928745fec9864498853d5aab49785840b12643e40c6518daf01218b5255a727d32ef9a3738e3766103679cfdcd6f1b320e272920f3b68 languageName: node linkType: hard @@ -15937,12 +15951,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.1": - version: 6.13.1 - resolution: "qs@npm:6.13.1" +"qs@npm:6.14.0": + version: 6.14.0 + resolution: "qs@npm:6.14.0" dependencies: - side-channel: "npm:^1.0.6" - checksum: 10/53cf5fdc5f342a9ffd3968f20c8c61624924cf928d86fff525240620faba8ca5cfd6c3f12718cc755561bfc3dc9721bc8924e38f53d8925b03940f0b8a902212 + side-channel: "npm:^1.1.0" + checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 languageName: node linkType: hard @@ -16210,27 +16224,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:6.28.1": - version: 6.28.1 - resolution: "react-router-dom@npm:6.28.1" +"react-router-dom@npm:6.28.2": + version: 6.28.2 + resolution: "react-router-dom@npm:6.28.2" dependencies: - "@remix-run/router": "npm:1.21.0" - react-router: "npm:6.28.1" + "@remix-run/router": "npm:1.21.1" + react-router: "npm:6.28.2" peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 10/ce00a6e89add5aeed0f3c881714752be8642ad1e8dd70d337a6bb71b59656afc0385b730bfad21d094f198d0497eb47ed930d8be09ce50e6dcc1eea6c9ab79a2 + checksum: 10/4775cb484c497be5833ef5e048378d685f970a72c75a595c8b74fce147eda9e705d885b71d888b1090a8f22e7630adb851766d34e771c3a649f73171f0fa4c2b languageName: node linkType: hard -"react-router@npm:6.28.1": - version: 6.28.1 - resolution: "react-router@npm:6.28.1" +"react-router@npm:6.28.2": + version: 6.28.2 + resolution: "react-router@npm:6.28.2" dependencies: - "@remix-run/router": "npm:1.21.0" + "@remix-run/router": "npm:1.21.1" peerDependencies: react: ">=16.8" - checksum: 10/5bf02fe9f43c49580ee162824c4e3597accbed37df8e0b0705d90f56c647ae2c4c19695fcef39ed2ea7434c6865b93afbddcf4643a5e51d77299577474070c39 + checksum: 10/4cf150e3762acff8a087d6b474861fdb73efdf829ce0619bc980f3d8fc5d9e45e67333ab7d62af5b775fba8efe8f8d342f089bec75f9b41f3162e139c0187efd languageName: node linkType: hard @@ -16684,7 +16698,7 @@ __metadata: jest: "npm:29.7.0" react: "npm:18.3.1" react-dom: "npm:18.3.1" - typescript: "npm:5.7.2" + typescript: "npm:5.7.3" webpack: "npm:5.97.1" webpack-dev-server: "npm:5.2.0" languageName: unknown @@ -17624,10 +17638,10 @@ __metadata: linkType: hard "storybook@npm:^8.0.4": - version: 8.4.7 - resolution: "storybook@npm:8.4.7" + version: 8.5.0 + resolution: "storybook@npm:8.5.0" dependencies: - "@storybook/core": "npm:8.4.7" + "@storybook/core": "npm:8.5.0" peerDependencies: prettier: ^2 || ^3 peerDependenciesMeta: @@ -17637,7 +17651,7 @@ __metadata: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 10/827979504f98b69397bf91c395d0eea030d5702d0d28ccea4919a5037f628038129b287113aec9d8ecd1062e40b8b22423a300a32381c2d0b340b6960e3b42ea + checksum: 10/7d0de8d739e600ffc45f4f0ab6bf698876a3aa4f63ad9d2b64bdada3816b77a19d01aee672d0c13d94a21072648b3c852b94a43eda4bd67af15437465c69244d languageName: node linkType: hard @@ -18801,23 +18815,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.7.2": - version: 5.7.2 - resolution: "typescript@npm:5.7.2" +"typescript@npm:5.7.3": + version: 5.7.3 + resolution: "typescript@npm:5.7.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/4caa3904df69db9d4a8bedc31bafc1e19ffb7b24fbde2997a1633ae1398d0de5bdbf8daf602ccf3b23faddf1aeeb9b795223a2ed9c9a4fdcaf07bfde114a401a + checksum: 10/6a7e556de91db3d34dc51cd2600e8e91f4c312acd8e52792f243c7818dfadb27bae677175fad6947f9c81efb6c57eb6b2d0c736f196a6ee2f1f7d57b74fc92fa languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.7.2#optional!builtin<compat/typescript>": - version: 5.7.2 - resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin<compat/typescript>::version=5.7.2&hash=5786d5" +"typescript@patch:typescript@npm%3A5.7.3#optional!builtin<compat/typescript>": + version: 5.7.3 + resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin<compat/typescript>::version=5.7.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/d75ca10141afc64fd3474b41a8b082b640555bed388d237558aed64e5827ddadb48f90932c7f4205883f18f5bcab8b6a739a2cfac95855604b0dfeb34bc2f3eb + checksum: 10/dc58d777eb4c01973f7fbf1fd808aad49a0efdf545528dab9b07d94fdcb65b8751742804c3057e9619a4627f2d9cc85547fdd49d9f4326992ad0181b49e61d81 languageName: node linkType: hard