Skip to content

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

Merged
ivarne merged 11 commits into
mainfrom
fix/expression-work
May 27, 2026
Merged

ivarne merged 11 commits into
mainfrom
fix/expression-work

Conversation

@ivarne

@ivarne ivarne commented May 11, 2026 •

Copy link
Copy Markdown
Member

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

    • Layout and expression evaluation now use a unified data-accessor approach; legacy state paths are being phased out.
    • Form-data wrappers and factory now carry explicit storage metadata so wrapper copies preserve element context.
  • Tests

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

    • API endpoints and internal patching now 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

coderabbitai Bot commented May 11, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

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.
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.

Form-data Wrapper and Factory Changes

Layer / File(s) Summary
Factory signature and usage
src/Altinn.App.Core/Internal/Data/FormDataWrapperFactory.cs
Factory registration delegate now receives (object, DataType, DataElement?) and Create(...) forwards dataType/dataElement to registered factories or ReflectionFormDataWrapper fallback.
Wrapper interface
src/Altinn.App.Core/Internal/Data/IFormDataWrapper.cs
IFormDataWrapper now exposes DataType and nullable DataElement properties.
Reflection wrapper
src/Altinn.App.Core/Internal/Data/ReflectionFormDataWrapper.cs
Constructor updated to (object dataModel, DataType dataType, DataElement? dataElement = null) and exposes DataType/DataElement. Copy() preserves metadata.
Callsite updates
multiple files (InstanceDataUnitOfWork, controllers, services, tests)
Callsites updated to call FormDataWrapperFactory.Create(model, dataType, dataElement) where context is available. Generated wrappers updated to carry DataType/DataElement.

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; GetFormDataWrapper() forwards dataType/dataElement to factory.
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 and other validator tests updated to use enhanced fake and obtain evaluator state via dataAccessor.GetLayoutEvaluatorState().
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 exported API changes: extension method, simplified validator constructor, LayoutEvaluatorState.DataAccessor, ComponentContext new constructor and property, updated BaseLayoutComponent overload, updated component GetContext signatures, and obsolete translation overload text.

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels: feature

Suggested reviewers:

  • bjorntore
✨ Finishing Touches
🧪 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

Copilot AI left a comment

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.

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

@coderabbitai coderabbitai Bot left a comment

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.

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 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

@coderabbitai coderabbitai Bot left a comment

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.

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
+public class SubFormTests : IClassFixture, 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

@martinothamar martinothamar left a comment

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.

looks like a good improvement to me 👍

@olavsorl olavsorl left a comment

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.

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.

Comment thread src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs
Comment thread src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs
Comment thread src/Altinn.App.Core/Models/Expressions/ComponentContext.cs
Comment thread src/Altinn.App.Core/Internal/Data/FormDataWrapperFactory.cs
Comment thread src/Altinn.App.Core/Internal/Data/ReflectionFormDataWrapper.cs
Comment thread src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs Outdated
Comment thread src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs (1)

337-337: ⚡ Quick win

Avoid manually throwing NullReferenceException.

NullReferenceException is a system exception indicating an unintended null dereference. Per .NET guidelines, it should not be thrown explicitly. Consider using InvalidOperationException instead for consistency with the exception on lines 341-343 and to follow the established convention.

This also applies to line 304 which uses the same pattern, though it predates this change.

♻️ Suggested fix
         var formDataWrapper =
             await DataAccessor.GetFormDataWrapper(binding, dataElementIdentifier)
-            ?? throw new NullReferenceException($"No data element found for {binding.DataType} {binding.Field}");
+            ?? throw new InvalidOperationException($"No data element found for {binding.DataType} {binding.Field}");

And similarly for line 304:

         var formDataWrapper =
             await DataAccessor.GetFormDataWrapper(binding, context.DataElementIdentifier)
-            ?? throw new NullReferenceException($"No data element found for {binding.DataType} {binding.Field}");
+            ?? throw new InvalidOperationException($"No data element found for {binding.DataType} {binding.Field}");
🤖 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 `@src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs` at line
337, In LayoutEvaluatorState (replace occurrences where code does "?? throw new
NullReferenceException($"No data element found for {binding.DataType}
{binding.Field}")"), change the thrown exception to InvalidOperationException
with the same message; do the same replacement for the similar occurrence around
the earlier null-check (the one referenced at line 304) so both places throw new
InvalidOperationException($"No data element found for {binding.DataType}
{binding.Field}") instead of NullReferenceException to match the project's
exception convention.
🤖 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.

Nitpick comments:
In `@src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs`:
- Line 337: In LayoutEvaluatorState (replace occurrences where code does "??
throw new NullReferenceException($"No data element found for {binding.DataType}
{binding.Field}")"), change the thrown exception to InvalidOperationException
with the same message; do the same replacement for the similar occurrence around
the earlier null-check (the one referenced at line 304) so both places throw new
InvalidOperationException($"No data element found for {binding.DataType}
{binding.Field}") instead of NullReferenceException to match the project's
exception convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bab36f7a-fd6f-4d46-bac8-f39da1367b3c

📥 Commits

Reviewing files that changed from the base of the PR and between 31456df and 4cfe59c.

📒 Files selected for processing (4)
  • src/Altinn.App.Api/Controllers/DataController.cs
  • src/Altinn.App.Core/Internal/Data/InstanceDataUnitOfWork.cs
  • src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Altinn.App.Api/Controllers/DataController.cs
  • src/Altinn.App.Core/Internal/Data/InstanceDataUnitOfWork.cs
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt

@ivarne
ivarne enabled auto-merge (squash) May 27, 2026 09:58
@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

@ivarne
ivarne merged commit 9a5f0df into main May 27, 2026
12 of 13 checks passed
@ivarne
ivarne deleted the fix/expression-work branch May 27, 2026 10:08
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