Skip to content

Go a few steps further in unifying IInstanceDataAccessor and LayoutEvaluatorState#1747

Open
ivarne wants to merge 8 commits into
mainfrom
fix/expression-work
Open

Go a few steps further in unifying IInstanceDataAccessor and LayoutEvaluatorState#1747
ivarne wants to merge 8 commits into
mainfrom
fix/expression-work

Conversation

@ivarne
Copy link
Copy Markdown
Member

@ivarne ivarne commented May 11, 2026

LayoutEvaluatorState is clunky and IInstanceDataAccessor can easily incorporate the same functionality.

This PR solves the following two issues

  • Make two way conversion between state and accessor easier by adding a public DataAccessor property on LayoutEvaluatorState
  • Let ComponentContext keep a reference to the DataAccessor instead of the layoutEvaluatorState.

Obviously most of the work went into making the tests work again now that state and accessor needs to be initialised from each other.

Verification

  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)
  • All tests run green

Documentation

  • User documentation is updated with a separate linked PR in altinn-studio-docs. (if applicable)

Summary by CodeRabbit

  • Refactor

    • Evaluation and translation now drive layout/expression contexts from a unified data-accessor API; legacy state-based paths are being phased out.
    • Form-data wrappers and factory now include explicit storage metadata (data type and data element) so wrappers preserve element context.
  • Tests

    • Test helpers and suites updated to use the accessor-driven setup and richer test context.
  • Bug Fixes

    • Controllers and patching preserve element metadata when storing or updating form data.

Review Change Stack

Copilot AI review requested due to automatic review settings May 11, 2026 10:03
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c5e24b4-84c5-40f5-b986-daaffba44480

📥 Commits

Reviewing files that changed from the base of the PR and between 766ae2c and 31456df.

⛔ Files ignored due to path filters (1)
  • test/Altinn.App.SourceGenerator.Integration.Tests/gen/Altinn.App.Analyzers/Altinn.App.Analyzers.FormDataWrapperGenerator/SkjemaFormDataWrapper.g.cs is excluded by !**/gen/**
📒 Files selected for processing (7)
  • src/Altinn.App.Core/Internal/Data/InstanceDataUnitOfWork.cs
  • src/Altinn.App.Core/Internal/Data/PreviousDataAccessor.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/SubForm/SubFormTests.cs
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt
  • test/Altinn.App.SourceGenerator.Tests/FullTests.Run#SkjemaFormDataWrapper.g.verified.cs
  • test/Altinn.App.SourceGenerator.Tests/FullTests.cs
  • test/Altinn.App.SourceGenerator.Tests/SourceTextGeneratorTests.Generate.verified.cs
✅ Files skipped from review due to trivial changes (2)
  • test/Altinn.App.SourceGenerator.Tests/FullTests.Run#SkjemaFormDataWrapper.g.verified.cs
  • test/Altinn.App.SourceGenerator.Tests/SourceTextGeneratorTests.Generate.verified.cs

📝 Walkthrough

<review_stack_artifact>

</review_stack_artifact>

Walkthrough

This PR refactors the layout evaluation APIs to be IInstanceDataAccessor-centric instead of LayoutEvaluatorState-centric. Core state models expose the accessor as a public property, component context construction accepts the accessor as the primary parameter, and all layout components are updated to implement the new API. State initialization, validators, and services are adapted accordingly. Test infrastructure is expanded to provide richer evaluation context via enhanced fake accessors.

Changes

Core State Models and Public API Refactoring

Layer / File(s) Summary
Data Accessor Property
src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs
LayoutEvaluatorState exposes DataAccessor as a public property and refactors all internal methods to use the accessor instead of a private field. A new WithDataAccessor(...) method enables state wrapping with a different accessor while preserving configuration.
Component Context Constructor
src/Altinn.App.Core/Models/Expressions/ComponentContext.cs
ComponentContext gains a primary constructor accepting IInstanceDataAccessor. The LayoutEvaluatorState-based constructor is marked obsolete and delegates to the new overload. State property becomes computed from DataAccessor.GetLayoutEvaluatorState().
Accessor Extension Method
src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
GetFormDataWrapper extension method added to resolve form data wrappers by ModelBinding and default identifier, enforcing MaxCount == 1 constraints for non-default types.
Translation Service Deprecation
src/Altinn.App.Core/Internal/Texts/ITranslationService.cs
TranslateTextKey overload accepting LayoutEvaluatorState is marked [Obsolete] with guidance to use the IInstanceDataAccessor overload instead.
Base Component API Update
src/Altinn.App.Core/Models/Layout/Components/Base/BaseLayoutComponent.cs
New abstract GetContext(IInstanceDataAccessor ...) overload defined; LayoutEvaluatorState overload marked obsolete with error to force migration.

State Initialization and Dependencies

Layer / File(s) Summary
Legacy Accessor Enrichment
src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs
SingleDataElementAccessor constructor expanded to accept LayoutModel, FrontEndSettings, ITranslationService, and context parameters (gatewayAction, taskId, language). GetLayoutEvaluatorState() now constructs a LayoutEvaluatorState instead of throwing.
PreviousDataAccessor Implementation
src/Altinn.App.Core/Internal/Data/PreviousDataAccessor.cs
GetLayoutEvaluatorState() delegates to the wrapped accessor and returns a new state via WithDataAccessor(this) for proper wrapping.
Validator Dependency Removal
src/Altinn.App.Core/Features/Validation/Default/RequiredLayoutValidator.cs
RequiredLayoutValidator constructor simplified to accept only IAppResources, removing ILayoutEvaluatorStateInitializer. Validation now retrieves state directly via dataAccessor.GetLayoutEvaluatorState().

Component Implementations

Layer / File(s) Summary
Simple Components
src/Altinn.App.Core/Models/Layout/Components/Base/NoReferenceComponent.cs
GetContext updated to accept IInstanceDataAccessor dataAccessor and construct ComponentContext using the accessor.
Child Resolution
src/Altinn.App.Core/Models/Layout/Components/Base/ReferenceComponent.cs
GetContext and GetClaimedChildComponentContext updated to accept IInstanceDataAccessor and to pass the accessor into child component GetContext calls.
Group-Backed Options
src/Altinn.App.Core/Models/Layout/Components/OptionsComponent.cs
GetContext accepts IInstanceDataAccessor, determining row count via dataAccessor.GetFormDataWrapper(...).GetRowCount() for group-backed options.
Repeating Groups
src/Altinn.App.Core/Models/Layout/Components/RepeatingGroupComponent.cs
GetContext accepts IInstanceDataAccessor, computing row count from the form data wrapper and creating per-row contexts.
SubForms
src/Altinn.App.Core/Models/Layout/Components/SubFormComponent.cs
GetContext accepts IInstanceDataAccessor, enumerating data via dataAccessor.Instance.Data and passing the accessor to child page context creation.
Pages and Layout
src/Altinn.App.Core/Models/Layout/Components/PageComponent.cs, src/Altinn.App.Core/Models/Layout/LayoutModel.cs
GetContextForPage and GenerateComponentContexts signatures updated to accept IInstanceDataAccessor instead of LayoutEvaluatorState.

Service and Orchestration Updates

Layer / File(s) Summary
Expression Validation
src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs
ValidateFormData constructs ComponentContext using dataAccessor instead of evaluatorState.
Gateway Evaluation
src/Altinn.App.Core/Internal/Process/ExpressionsExclusiveGateway.cs
EvaluateSequenceFlow constructs ComponentContext using state.DataAccessor instead of the full LayoutEvaluatorState.
PDF Service
src/Altinn.App.Core/Internal/Pdf/PdfService.cs
GetVariableSubstitutedFileName constructs ComponentContext directly from dataAccessor, removing explicit state validation logic.

Test Infrastructure and Utilities

Layer / File(s) Summary
Fake Accessor Enhancement
test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/InstanceDataAccessorFake.cs
Constructor expanded to accept ITranslationService?, LayoutModel?, FrontEndSettings?, gatewayAction, and language. GetLayoutEvaluatorState() implemented to construct a complete LayoutEvaluatorState with validation of required dependencies.
Test Helper Builders
test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/DynamicClassBuilder.cs
DataAccessorFromJsonDocument overloads updated to accept richer context and wire it into InstanceDataAccessorFake.
Validator and Legacy Tests
test/Altinn.App.Core.Tests/Features/Validators/...
ExpressionValidatorTests refactored to use enhanced fake with translationservice; legacy validator tests updated to pass additional parameters for context initialization.
Expression Tests
test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/..., test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/...
Test setup refactored to use accessor-based state construction, mocked ITranslationService, and proper dependency wiring through DynamicClassBuilder.
Public API Snapshot
test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt
Updated to reflect all exported API surface changes: new extension method, simplified validator constructor, new properties, updated component signatures, and deprecation notices.

🎯 4 (Complex) | ⏱️ ~60 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/expression-work

Comment thread test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestFunctions.cs Dismissed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR continues the effort to phase out LayoutEvaluatorState in favor of IInstanceDataAccessor by making state/accessor conversion easier and shifting component/context APIs to primarily use the accessor.

Changes:

  • Added LayoutEvaluatorState.DataAccessor and updated ComponentContext/layout components to accept and carry IInstanceDataAccessor instead of LayoutEvaluatorState.
  • Updated layout context generation APIs (LayoutModel.GenerateComponentContexts, BaseLayoutComponent.GetContext, etc.) to take an accessor.
  • Refactored test utilities and many layout-expression tests to initialize state via accessor and keep tests passing.

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt Updates public API snapshot to reflect new/changed members and obsolete overloads.
test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/InstanceDataAccessorFake.cs Extends fake accessor to be able to produce a LayoutEvaluatorState (and hold language/layout/text deps).
test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/DynamicClassBuilder.cs Updates helpers to construct InstanceDataAccessorFake with new dependencies.
test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/SubForm/SubFormTests.cs Updates full tests to provide layout/state-related dependencies to the accessor fake.
test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/RepeatingGroupsHidden/RepeatingGroupsHidden.cs Simplifies test setup to use data object directly rather than accessor.
test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/RemoveHiddenData/RemoveHiddenDataTests.cs Simplifies test setup to use data object directly rather than accessor.
test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LikertHidden/LikertHiddenTests.cs Updates helper invocation to new DataAccessorFromJsonDocument signature.
test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LayoutTestUtils.cs Updates shared test initialization to pass translation/layout/frontend settings into accessor fake.
test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestInvalid.cs Updates expression test harness to create state via accessor.
test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestFunctions.cs Updates expression test harness to construct accessor with translation/layout and use new context generation API.
test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestContextList.cs Updates context-list test harness to create state via accessor.
test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestBackendExclusiveFunctions.cs Updates backend-exclusive test harness to create state via accessor.
test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/ExpressionTestCaseRoot.cs Adjusts context creation for new ComponentContext ctor signature (accessor-first).
test/Altinn.App.Core.Tests/Features/Validators/ValidationServiceTests.cs Updates validator tests to construct accessor fake with additional constructor params.
test/Altinn.App.Core.Tests/Features/Validators/LegacyValidationServiceTests/ValidationServiceOldTests.cs Updates legacy validator test to new accessor fake constructor signature.
test/Altinn.App.Core.Tests/Features/Validators/Default/LegacyIValidationFormDataTests.cs Updates legacy IValidation tests to new accessor fake constructor signature.
test/Altinn.App.Core.Tests/Features/Validators/Default/ExpressionValidatorTests.cs Updates expression validator tests to build evaluator state via accessor fake.
src/Altinn.App.Core/Models/Layout/LayoutModel.cs Changes context generation entrypoint to accept IInstanceDataAccessor.
src/Altinn.App.Core/Models/Layout/Components/SubFormComponent.cs Switches context-building from state to accessor and updates instance data access accordingly.
src/Altinn.App.Core/Models/Layout/Components/RepeatingGroupComponent.cs Switches to accessor-based context building and uses accessor wrapper for row counting.
src/Altinn.App.Core/Models/Layout/Components/PageComponent.cs Switches page context building to accept accessor.
src/Altinn.App.Core/Models/Layout/Components/OptionsComponent.cs Switches options context building to accessor and uses accessor wrapper for row counting.
src/Altinn.App.Core/Models/Layout/Components/Base/ReferenceComponent.cs Switches claimed-child context building to accessor-based flow.
src/Altinn.App.Core/Models/Layout/Components/Base/NoReferenceComponent.cs Switches trivial context creation to accessor-based flow.
src/Altinn.App.Core/Models/Layout/Components/Base/BaseLayoutComponent.cs Introduces new accessor-based abstract GetContext and adds an obsolete state-based overload.
src/Altinn.App.Core/Models/Expressions/ComponentContext.cs Adds accessor-based ctor and stores accessor; makes State derived from accessor.
src/Altinn.App.Core/Internal/Texts/ITranslationService.cs Obsoletes translation overload taking LayoutEvaluatorState.
src/Altinn.App.Core/Internal/Process/ExpressionsExclusiveGateway.cs Constructs ComponentContext with accessor rather than state.
src/Altinn.App.Core/Internal/Pdf/PdfService.cs Uses translation overload taking IInstanceDataAccessor and builds ComponentContext with accessor.
src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs Updates internal accessor used by initializer to be able to produce a LayoutEvaluatorState.
src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs Adds DataAccessor property; routes context generation through accessor; adds WithDataAccessor.
src/Altinn.App.Core/Internal/Data/PreviousDataAccessor.cs Implements GetLayoutEvaluatorState() by reusing the underlying state with a new accessor.
src/Altinn.App.Core/Features/Validation/Default/RequiredLayoutValidator.cs Stops using initializer and instead requires dataAccessor.GetLayoutEvaluatorState().
src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs Builds ComponentContext with accessor instead of state.
src/Altinn.App.Core/Features/IInstanceDataAccessor.cs Adds helper to resolve IFormDataWrapper from a ModelBinding + default data element identifier.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Altinn.App.Core/Internal/Data/PreviousDataAccessor.cs
Comment thread src/Altinn.App.Core/Models/Expressions/ComponentContext.cs
@ivarne ivarne added backport-ignore This PR is a new feature and should not be cherry-picked onto release branches other A PR that should be in release notes, but as a chore labels May 11, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
test/Altinn.App.Core.Tests/Features/Validators/LegacyValidationServiceTests/ValidationServiceOldTests.cs (1)

180-190: ⚡ Quick win

Strongly recommend using named parameters for clarity.

This constructor call has 9 positional parameters with 5 consecutive null values, making it very difficult to understand which parameter serves what purpose. The trailing string literals ("Task_1", "test") are particularly opaque without documentation.

♻️ Recommended refactor using named parameters
-var dataAccessorMock = new InstanceDataAccessorFake(
-    instance,
-    _applicationMetadata,
-    null,
-    null,
-    null,
-    null,
-    null,
-    "Task_1",
-    "test"
-)
+var dataAccessorMock = new InstanceDataAccessorFake(
+    instance: instance,
+    applicationMetadata: _applicationMetadata,
+    translationService: null,
+    layoutModel: null,
+    frontEndSettings: null,
+    gatewayAction: null,
+    language: null,
+    taskId: "Task_1",
+    dataType: "test"
+)

This significantly improves readability and reduces the risk of passing arguments in the wrong order.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@test/Altinn.App.Core.Tests/Features/Validators/LegacyValidationServiceTests/ValidationServiceOldTests.cs`
around lines 180 - 190, The constructor call to InstanceDataAccessorFake with
nine positional args (including five consecutive nulls and the trailing
"Task_1", "test") is unclear; update the invocation to use named parameters for
the InstanceDataAccessorFake constructor so each argument is explicit (e.g.,
name the parameters corresponding to instance, applicationMetadata, the optional
values currently passed as null, taskId/"Task_1", and user/"test"), replacing
the positional nulls with their parameter names to improve readability and
prevent ordering mistakes.
test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LikertHidden/LikertHiddenTests.cs (1)

50-58: ⚡ Quick win

Consider using named parameters for clarity.

The constructor call has 7 positional parameters, 6 of which are null. This makes it difficult to understand which parameter is which and increases the risk of passing arguments in the wrong order.

Consider using named parameters to improve readability:

IInstanceDataAccessor dataAccessor = DynamicClassBuilder.DataAccessorFromJsonDocument(
    instance: new Instance(),
    translationService: null,
    layoutModel: null,
    frontEndSettings: null,
    dataModel: jsonDoc.RootElement,
    gatewayAction: null,
    language: null
);

Alternatively, if this pattern is common across tests, consider providing overloads or a builder pattern in DynamicClassBuilder to reduce the need for explicit nulls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LikertHidden/LikertHiddenTests.cs`
around lines 50 - 58, Replace the 7 positional arguments in the call to
DynamicClassBuilder.DataAccessorFromJsonDocument with named parameters to make
it clear which values are null and which is jsonDoc.RootElement; for example use
parameter names like instance:, translationService:, layoutModel:,
frontEndSettings:, dataModel:, gatewayAction:, language: so the call to
DataAccessorFromJsonDocument clearly associates new Instance() and
jsonDoc.RootElement with the correct parameters rather than relying on
positional nulls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Altinn.App.Core/Features/Validation/Default/RequiredLayoutValidator.cs`:
- Line 18: The change removed the fallback that builds a LayoutEvaluatorState
from the current taskId and language, causing Validate to rely solely on
pre-attached accessor state; restore the fallback in RequiredLayoutValidator so
that if the ILayoutEvaluatorStateAccessor has no eager state the constructor or
Validate method will construct a new LayoutEvaluatorState using the provided
taskId and language parameters (use the existing LayoutEvaluatorState type and
the accessor methods to attach it), ensuring
RequiredLayoutValidator.Validate(taskId, language, ...) uses the
task/language-aware state when available or creates and attaches one when
missing.

In `@src/Altinn.App.Core/Models/Expressions/ComponentContext.cs`:
- Around line 19-27: The obsolete ComponentContext constructor discards the
incoming LayoutEvaluatorState causing State to call
DataAccessor.GetLayoutEvaluatorState() on every access and lose memoized caches;
modify the obsolete constructor overload in ComponentContext to store the
provided LayoutEvaluatorState (e.g., assign a private readonly field backing
State) and update the State property or accessor to return the stored instance
when present instead of always reconstructing via
DataAccessor.GetLayoutEvaluatorState(), ensuring the legacy path preserves and
reuses the original state and its caches (also apply the same change to the
other obsolete constructor at the other occurrence).

In
`@test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/DynamicClassBuilder.cs`:
- Around line 168-186: The DataAccessorFromJsonDocument helper is ignoring its
gatewayAction and language parameters by passing null to the
InstanceDataAccessorFake constructor; update the call inside
DataAccessorFromJsonDocument to forward the gatewayAction and language arguments
(replace gatewayAction: null, language: null with gatewayAction: gatewayAction,
language: language) so tests exercise action- and language-dependent behavior in
InstanceDataAccessorFake.

In
`@test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt`:
- Around line 4766-4768: The Obsolete attribute on the legacy GetContext
overload (the method signature
GetContext(Altinn.App.Core.Internal.Expressions.LayoutEvaluatorState state,
Altinn.App.Core.Models.DataElementIdentifier defaultDataElementIdentifier,
int[]? rowIndexes, System.Collections.Generic.Dictionary<string,
Altinn.App.Core.Models.Layout.LayoutSetComponent> layoutsLookup)) currently uses
the 'true' flag which makes it a compilation error; change the attribute to mark
it obsolete but not an error (set the error flag to false or remove the true
argument) so consumers with custom BaseLayoutComponent implementations can
compile for one transition cycle.
- Line 4912: Add a deprecated bridge overload on LayoutModel: implement
GenerateComponentContexts(LayoutEvaluatorState) as a thin wrapper that is
annotated [Obsolete] (with a migration message) and forwards to the new
GenerateComponentContexts(IInstanceDataAccessor) implementation; inside the
wrapper obtain or adapt an IInstanceDataAccessor from the supplied
LayoutEvaluatorState (use the same adapter/conversion logic your codebase uses
for converting LayoutEvaluatorState to IInstanceDataAccessor) and return the
result of calling GenerateComponentContexts(IInstanceDataAccessor) so existing
callers keep working while steering them to the new API.

---

Nitpick comments:
In
`@test/Altinn.App.Core.Tests/Features/Validators/LegacyValidationServiceTests/ValidationServiceOldTests.cs`:
- Around line 180-190: The constructor call to InstanceDataAccessorFake with
nine positional args (including five consecutive nulls and the trailing
"Task_1", "test") is unclear; update the invocation to use named parameters for
the InstanceDataAccessorFake constructor so each argument is explicit (e.g.,
name the parameters corresponding to instance, applicationMetadata, the optional
values currently passed as null, taskId/"Task_1", and user/"test"), replacing
the positional nulls with their parameter names to improve readability and
prevent ordering mistakes.

In
`@test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LikertHidden/LikertHiddenTests.cs`:
- Around line 50-58: Replace the 7 positional arguments in the call to
DynamicClassBuilder.DataAccessorFromJsonDocument with named parameters to make
it clear which values are null and which is jsonDoc.RootElement; for example use
parameter names like instance:, translationService:, layoutModel:,
frontEndSettings:, dataModel:, gatewayAction:, language: so the call to
DataAccessorFromJsonDocument clearly associates new Instance() and
jsonDoc.RootElement with the correct parameters rather than relying on
positional nulls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 174853e8-030e-408f-a75e-8d1020c24b63

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0a709 and f140fb9.

📒 Files selected for processing (35)
  • src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
  • src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs
  • src/Altinn.App.Core/Features/Validation/Default/RequiredLayoutValidator.cs
  • src/Altinn.App.Core/Internal/Data/PreviousDataAccessor.cs
  • src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs
  • src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs
  • src/Altinn.App.Core/Internal/Pdf/PdfService.cs
  • src/Altinn.App.Core/Internal/Process/ExpressionsExclusiveGateway.cs
  • src/Altinn.App.Core/Internal/Texts/ITranslationService.cs
  • src/Altinn.App.Core/Models/Expressions/ComponentContext.cs
  • src/Altinn.App.Core/Models/Layout/Components/Base/BaseLayoutComponent.cs
  • src/Altinn.App.Core/Models/Layout/Components/Base/NoReferenceComponent.cs
  • src/Altinn.App.Core/Models/Layout/Components/Base/ReferenceComponent.cs
  • src/Altinn.App.Core/Models/Layout/Components/OptionsComponent.cs
  • src/Altinn.App.Core/Models/Layout/Components/PageComponent.cs
  • src/Altinn.App.Core/Models/Layout/Components/RepeatingGroupComponent.cs
  • src/Altinn.App.Core/Models/Layout/Components/SubFormComponent.cs
  • src/Altinn.App.Core/Models/Layout/LayoutModel.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/ExpressionValidatorTests.cs
  • test/Altinn.App.Core.Tests/Features/Validators/Default/LegacyIValidationFormDataTests.cs
  • test/Altinn.App.Core.Tests/Features/Validators/LegacyValidationServiceTests/ValidationServiceOldTests.cs
  • test/Altinn.App.Core.Tests/Features/Validators/ValidationServiceTests.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/ExpressionTestCaseRoot.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestBackendExclusiveFunctions.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestContextList.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestFunctions.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/TestInvalid.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LayoutTestUtils.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LikertHidden/LikertHiddenTests.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/RemoveHiddenData/RemoveHiddenDataTests.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/RepeatingGroupsHidden/RepeatingGroupsHidden.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/SubForm/SubFormTests.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/DynamicClassBuilder.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/InstanceDataAccessorFake.cs
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt

Comment thread src/Altinn.App.Core/Models/Expressions/ComponentContext.cs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/SubForm/SubFormTests.cs (1)

417-421: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace finalizer-based mock verification with IDisposable.Dispose().

Using ~SubFormTests() for _appResourcesMock.Verify() and _appMetadataMock.Verify() is nondeterministic—finalizers run on arbitrary threads and can cause flaky or late test failures. xUnit calls Dispose() on IDisposable test classes after each test, providing predictable verification timing. A similar pattern exists in test/Altinn.App.Api.Tests/Controllers/DataController_PatchTests.cs:1115.

Suggested fix for SubFormTests
-public class SubFormTests : IClassFixture<DataAnnotationsTestFixture>
+public class SubFormTests : IClassFixture<DataAnnotationsTestFixture>, IDisposable
 {
@@
-    ~SubFormTests()
+    public void Dispose()
     {
-        _appResourcesMock?.Verify();
-        _appMetadataMock?.Verify();
+        _appResourcesMock.Verify();
+        _appMetadataMock.Verify();
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/SubForm/SubFormTests.cs`
around lines 417 - 421, The test class currently verifies mocks in the finalizer
(~SubFormTests()), which is nondeterministic; change the class to implement
IDisposable and move the verification calls into a public void Dispose() method
that calls _appResourcesMock?.Verify() and _appMetadataMock?.Verify(), and
remove the finalizer. Ensure the class signature and any constructors are
updated so xUnit will call Dispose() after each test (mirror the pattern used in
DataController_PatchTests).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/Altinn.App.Clients.Fiks.Tests/FiksArkiv/FiksArkivConfigResolverTest.cs`:
- Line 237: The test is passing null-forgiving values into
FormDataWrapperFactory.Create which masks nullability and metadata-dependent
bugs; update the two calls that currently pass null! (including the call at the
highlighted location using .ReturnsAsync(FormDataWrapperFactory.Create(model,
null!, null))) to pass the actual test metadata variables modelDataType and
modelDataElement instead so the wrapper mock receives proper
DataType/DataElement values (ensure both occurrences—the one around line 237 and
the similar call around line 364—are changed to
FormDataWrapperFactory.Create(model, modelDataType, modelDataElement)).

In
`@test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestFormDataWrapperFactory.cs`:
- Line 18: The test currently passes null! for the DataType argument to
FormDataWrapperFactory.Create (calls to FormDataWrapperFactory.Create(model,
null!, null)), which silences nullability but can break if wrapper metadata is
used; update both occurrences (the call at line ~18 and the similar call at line
~38) to supply a concrete DataType instance appropriate for the test (e.g., a
minimal/new DataType or a helper TestDataType object) instead of null!, so the
factory receives a valid DataType and the test remains robust.

In
`@test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestGeneratedGetter.cs`:
- Around line 53-54: Replace repeated null-suppressed metadata arguments with a
shared test DataType: add a single test field (e.g., private static readonly
DataType TestMetadata = new DataType(/* construct required fields */);) in the
TestGeneratedGetter test class and use TestMetadata instead of null! in all
wrapper constructor calls (the places using _skjema, null! for both generated
and reflection wrappers). Update every occurrence referenced in the comment
(lines around the constructor calls) to pass TestMetadata so null-forgiveness is
removed and both generated and reflection wrapper constructors receive the same
concrete metadata instance.

In
`@test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestGeneratedRemoval.cs`:
- Around line 66-67: The tests currently pass null! for the required
non-nullable DataType parameter to constructors like
ReflectionFormDataWrapper(...) and
Altinn_App_SourceGenerator_Integration_Tests_Models_SkjemaFormDataWrapper(...);
replace these null! usages with a minimal valid DataType created by a shared
test helper (e.g., TestDataTypeFactory.CreateDefault()) and update each affected
test file (TestGeneratedRemoval.cs, TestGeneratedCopy.cs, TestAltinnRowIds.cs,
TestGeneratedSetter.cs, TestDataModel.cs) to use that factory; alternatively, if
DataType is truly optional, change the constructor signatures and the DataType
type to DataType? and update the wrapper implementations to handle null safely,
but preferred is creating and passing a simple valid DataType instance via a
reusable test factory.

---

Outside diff comments:
In
`@test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/SubForm/SubFormTests.cs`:
- Around line 417-421: The test class currently verifies mocks in the finalizer
(~SubFormTests()), which is nondeterministic; change the class to implement
IDisposable and move the verification calls into a public void Dispose() method
that calls _appResourcesMock?.Verify() and _appMetadataMock?.Verify(), and
remove the finalizer. Ensure the class signature and any constructors are
updated so xUnit will call Dispose() after each test (mirror the pattern used in
DataController_PatchTests).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 74d569ef-90f4-49e4-bb4d-5e9b515f2c1f

📥 Commits

Reviewing files that changed from the base of the PR and between e13fc40 and 766ae2c.

⛔ Files ignored due to path filters (2)
  • test/Altinn.App.SourceGenerator.Integration.Tests/gen/Altinn.App.Analyzers/Altinn.App.Analyzers.FormDataWrapperGenerator/EmptyFormDataWrapper.g.cs is excluded by !**/gen/**
  • test/Altinn.App.SourceGenerator.Integration.Tests/gen/Altinn.App.Analyzers/Altinn.App.Analyzers.FormDataWrapperGenerator/SkjemaFormDataWrapper.g.cs is excluded by !**/gen/**
📒 Files selected for processing (38)
  • src/Altinn.App.Analyzers/SourceTextGenerator/CopyGenerator.cs
  • src/Altinn.App.Analyzers/SourceTextGenerator/SourceTextGenerator.cs
  • src/Altinn.App.Api/Controllers/ActionsController.cs
  • src/Altinn.App.Api/Controllers/DataController.cs
  • src/Altinn.App.Api/Helpers/Patch/InternalPatchService.cs
  • src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
  • src/Altinn.App.Core/Internal/Data/FormDataWrapperFactory.cs
  • src/Altinn.App.Core/Internal/Data/IFormDataWrapper.cs
  • src/Altinn.App.Core/Internal/Data/InstanceDataUnitOfWork.cs
  • src/Altinn.App.Core/Internal/Data/PreviousDataAccessor.cs
  • src/Altinn.App.Core/Internal/Data/ReflectionFormDataWrapper.cs
  • src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs
  • src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs
  • src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs
  • src/Altinn.App.Core/Models/Layout/Components/Base/ReferenceComponent.cs
  • test/Altinn.App.Clients.Fiks.Tests/FiksArkiv/FiksArkivConfigResolverTest.cs
  • test/Altinn.App.Core.Tests/Features/Validators/LegacyValidationServiceTests/ValidationServiceTests.cs
  • test/Altinn.App.Core.Tests/Features/Validators/ValidationServiceTests.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/CleanDataAccessor/TestValidateCleanData.CleanIncremental.verified.txt
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/CleanDataAccessor/TestValidateCleanData.DirtyIncremental.verified.txt
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/RequiredValidator/RequiredValidatorTests.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/SubForm/SubFormTests.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/Test2/FirstPage.json
  • test/Altinn.App.Core.Tests/LayoutExpressions/TestDataModel.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/InstanceDataAccessorFake.cs
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt
  • test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestAddIndexToPath.cs
  • test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestAltinnRowIds.cs
  • test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestFormDataWrapperFactory.cs
  • test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestGeneratedCopy.cs
  • test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestGeneratedGetter.cs
  • test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestGeneratedRemoval.cs
  • test/Altinn.App.SourceGenerator.Integration.Tests/UnitTest/TestGeneratedSetter.cs
  • test/Altinn.App.SourceGenerator.Tests/FullTests.Empty#SkjemaFormDataWrapper.g.verified.cs
  • test/Altinn.App.SourceGenerator.Tests/FullTests.Run#SkjemaFormDataWrapper.g.verified.cs
  • test/Altinn.App.SourceGenerator.Tests/FullTests.cs
  • test/Altinn.App.SourceGenerator.Tests/SourceTextGeneratorTests.Generate.verified.cs
  • test/Altinn.App.SourceGenerator.Tests/SourceTextGeneratorTests.GenerateEmpty.verified.cs
✅ Files skipped from review due to trivial changes (4)
  • test/Altinn.App.SourceGenerator.Tests/FullTests.Run#SkjemaFormDataWrapper.g.verified.cs
  • test/Altinn.App.SourceGenerator.Tests/SourceTextGeneratorTests.GenerateEmpty.verified.cs
  • test/Altinn.App.SourceGenerator.Tests/SourceTextGeneratorTests.Generate.verified.cs
  • test/Altinn.App.SourceGenerator.Tests/FullTests.Empty#SkjemaFormDataWrapper.g.verified.cs
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/Altinn.App.Core/Internal/Data/PreviousDataAccessor.cs
  • src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
  • src/Altinn.App.Core/Models/Layout/Components/Base/ReferenceComponent.cs
  • test/Altinn.App.Core.Tests/LayoutExpressions/TestUtilities/InstanceDataAccessorFake.cs
  • src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

Copy link
Copy Markdown
Contributor

@martinothamar martinothamar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like a good improvement to me 👍

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
54.0% Condition Coverage on New Code (required ≥ 65%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown
Contributor

@olavsorl olavsorl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Har bare lest gjennom koden uten å teste funksjonaliteten. La inn noen kommentarer blant annet på ting jeg tror vi kan kvitte oss med og et par kommentarer på dupliserte exception meldinger.

Dictionary<string, LayoutSetComponent> layoutsLookup
)
{
throw new NotImplementedException(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Så det er en breaking change dette?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hvis det bare skal kastes når den kalles. Så kan man kanskje likegodt bare kvitte seg med metoden?

{
throw new ArgumentException(
$"Attempted to claim child with id {componentId} to component {Id}, but it has already been claimed by {claimedComponent}."
$"""Attempted to claim child with id "{componentId}" to component "{Id}", but it has already been claimed by "{claimedComponent}"."""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$"""Attempted to claim child with id "{componentId}" to component "{Id}", but it has already been claimed by "{claimedComponent}"."""
$"Attempted to claim child with id {componentId} to component {Id}, but it has already been claimed by {claimedComponent}."

}
throw new ArgumentException(
$"Attempted to claim child with id {componentId} to component {Id}, but the componentId does not exist"
$"""Attempted to claim child with id "{componentId}" to component "{Id}", but the componentId does not exist"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$"""Attempted to claim child with id "{componentId}" to component "{Id}", but the componentId does not exist"""
$"Attempted to claim child with id {componentId} to component {Id}, but the componentId does not exist"

/// Provides access to the instance data accessor, enabling operations and interactions
/// with the underlying instance data used in layout evaluation and bindings.
/// </summary>
public IInstanceDataAccessor DataAccessor { get; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blir litt tydeligere kanskje om man benytter hele navnet her InstanceDataAccessor?

Suggested change
public IInstanceDataAccessor DataAccessor { get; }
public IInstanceDataAccessor InstanceDataAccessor { get; }

);
}
throw new InvalidOperationException($"Data element with type {key.DataType} not found on instance");
return new DataReference() { Field = field, DataElementIdentifier = formDataWrapper.DataElement };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utenfor scope av denne PRen, men DataElementIdentifier får jo flere egenskaper når man konstruerer den med DataElement, da får den også DataTypeId. Sikkert gode grunner til at det er gjort sånn, men det er vell ikke helt ideelt?

/// Provides access to the current <see cref="IInstanceDataAccessor"/> .
/// </summary>
public LayoutEvaluatorState State { get; }
public IInstanceDataAccessor DataAccessor { get; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Samme her, litt tydeligere dersom man kaller det InstanceDataAccessor

internal static IFormDataWrapper Create(object dataModel)
internal static IFormDataWrapper Create(object dataModel, DataType dataType, DataElement? dataElement)
{
if (_dataWrappers.TryGetValue(dataModel.GetType(), out var accessorType))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Register blir aldri kalt. Så det ser ut som vi egentlig kan kvitte oss med hele factoryen. Hva med å bare newe ReflectonFormDataWrapper direkte der hvor denne Create metoden blir kalt og fjerne denne klassen?

public Type BackingDataType => _dataModel.GetType();

/// <inheritdoc />
public DataType DataType { get; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Denne blir vell aldri referert annet enn i konstruktøren og i Copy metoden, kan antagelig fjernes

var formDataWrapper = await _dataAccessor.GetFormDataWrapper(dataElementId);
var formDataWrapper =
await DataAccessor.GetFormDataWrapper(binding, dataElementIdentifier)
?? throw new InvalidOperationException($"No data element found for {binding.DataType} {binding.Field}");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Det var sikkert ikke meninga at disse exceptionmeldingene skulle være identiske, denne og den på linje 339.

var formDataWrapper = await _dataAccessor.GetFormDataWrapper(dataElementId);
var formDataWrapper =
await DataAccessor.GetFormDataWrapper(binding, context.DataElementIdentifier)
?? throw new NullReferenceException($"No data element found for {binding.DataType} {binding.Field}");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Det var sikkert ikke meninga at disse exceptionmeldingene skulle være identiske, denne og den på linje 308.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-ignore This PR is a new feature and should not be cherry-picked onto release branches other A PR that should be in release notes, but as a chore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants