Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: standalone pipes and legacy external modules support in angular 19 #10826

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

KernelFolla
Copy link

this PR solves two things:

  1. angular standalone components in external legacy modules ( Add ngx-translate coverage in e2e #10752 )
  2. standalone pipes

I've tested the following code in my project without issues but I'm not confident on how to run and fix tests with karma and e2e

I hope this PR somehow contributes towards the definitive solution for supporting Angular 19🙏

@Martinspire
Copy link

Tests seem to fail, any idea on that?

@vzakharov-rxnt
Copy link

This 100% fixed all the relevant issues in my project. It's an enterprise class frontend app with probably 500k LOC of frontend code, legacy AJS + A19 hybrid. We should merge your PR ASAP.

@sumegha26
Copy link

any updates on this ?

@kheos31
Copy link

kheos31 commented Jan 31, 2025

Hello, do you find a way to correct the failing tests @KernelFolla ? Thanks for your contribution anyway, hoping it will be merged ASAP regarding of issues linked.

@KernelFolla
Copy link
Author

Hello, do you find a way to correct the failing tests @KernelFolla ? Thanks for your contribution anyway, hoping it will be merged ASAP regarding of issues linked.

can I say I don't know how to do it? :) anyway I'll try this weekend, any suggestions and help are welcome.

@sumegha26
Copy link

sumegha26 commented Jan 31, 2025

Any plans to merge this change ?

@kheos31
Copy link

kheos31 commented Feb 3, 2025

Hello, do you find a way to correct the failing tests @KernelFolla ? Thanks for your contribution anyway, hoping it will be merged ASAP regarding of issues linked.

can I say I don't know how to do it? :) anyway I'll try this weekend, any suggestions and help are welcome.

Yes you can! 🤣 I'm not used to work with NgMocks source code and I think @satanTime or maybe an usual contributor could help on it. As I read in your changed code and in the original code, I think there is a missing concept in isStandalone function.

There is no way currently for this function to know the using context of an entity (component, directive, pipe). Without this information, how NgMocks could know if an entity is standalone or not? If this function could know if the dependency tree entity to mock is declared by a Module, we could deduce that is not a standalone component. It could be summed up as :

if (isEntityDeclaredInModule) {    // <--- treatment for component/directive/pipe declared into module, concept currently absent
    return false;
} else {
   // do your current modified isStandalone treatment
}

Maybe i'm wrong but as NgMocks knows the full entity dependency tree, I think you could find this information and add it to the isStandalone function (or call the isStandalone function just in "else" case).

Good luck @KernelFolla 😉

@joepvl
Copy link

joepvl commented Mar 5, 2025

In general, this PR looks correct to me. So far the CI seems to have failed on a technical issue, not an actual test failure. Can you try re-running the build like the CircleCI UI suggests? Screenshot:
image

@FrankMerema
Copy link

There is no way currently for this function to know the using context of an entity (component, directive, pipe). Without this information, how NgMocks could know if an entity is standalone or not? If this function could know if the dependency tree entity to mock is declared by a Module, we could deduce that is not a standalone component. It could be summed up as :

if (isEntityDeclaredInModule) {    // <--- treatment for component/directive/pipe declared into module, concept currently absent
    return false;
} else {
   // do your current modified isStandalone treatment
}

Maybe i'm wrong but as NgMocks knows the full entity dependency tree, I think you could find this information and add it to the isStandalone function (or call the isStandalone function just in "else" case).

@kheos31 To be honest I don't fully understand what you're referring to here? Could you explain it a bit more?

I'm currently looking at the tests and I found something that is probably not correct in this PR. I'm trying to understand how everything works together in order to fix the code and with that the code :)

Pipe(coreReflectPipeResolve(pipe))(mock);
const meta = coreReflectPipeResolve(pipe);

Pipe(decorateDeclaration(pipe, mock, meta, {}) as Pipe)(mock);
Copy link

@FrankMerema FrankMerema Mar 6, 2025

Choose a reason for hiding this comment

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

Because of the decorateDeclaration call here, the decorateMock function will be called twice. Once inside the method and once on the line below (43). Therefore the contents of the mock object are overwritten the second time it's called and breaking the tests.

Copy link
Author

Choose a reason for hiding this comment

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

I noticed that line 42 (the line starting with 'Pipe') appears to be redundant and could potentially be removed without consequences. I plan to verify this hypothesis tomorrow when I'll have access to the code to test this change.

anyway changes on this file are all made in order to support standalone pipes

@@ -18,4 +18,9 @@ export default (mock: AnyType<any>, source: AnyType<any>, configInput: ngMocksMo
}
: configInput;
coreDefineProperty(mock.prototype, '__ngMocksConfig', config);
if ((mock as any)?.ɵmod?.declarations) {

Choose a reason for hiding this comment

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

Hi @KernelFolla, could you maybe elaborate a bit on this piece of code? I don't fully understand the purpose of it?

Copy link
Author

Choose a reason for hiding this comment

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

when mocking angular modules, this code ensures that declarations which don't explicitly specify standalone: true are marked as standalone: false

@@ -30,7 +30,7 @@ const registerTemplateMiddleware = (template: AnyType<any>, meta: Directive): vo
// nothing to do
}

const standalone = (meta as any).__ngMocksStandalone === true;
const standalone = (meta as any).standalone === true;

Choose a reason for hiding this comment

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

Why is this part needed? It breaks the test for #4032. Locally I've reverted this line and I don't see any difference.

Copy link
Author

Choose a reason for hiding this comment

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

Honestly I don't remember, tomorrow I'll try this too, reverting on my codebase

@kheos31
Copy link

kheos31 commented Mar 6, 2025

There is no way currently for this function to know the using context of an entity (component, directive, pipe). Without this information, how NgMocks could know if an entity is standalone or not? If this function could know if the dependency tree entity to mock is declared by a Module, we could deduce that is not a standalone component. It could be summed up as :

if (isEntityDeclaredInModule) {    // <--- treatment for component/directive/pipe declared into module, concept currently absent
    return false;
} else {
   // do your current modified isStandalone treatment
}

Maybe i'm wrong but as NgMocks knows the full entity dependency tree, I think you could find this information and add it to the isStandalone function (or call the isStandalone function just in "else" case).

@kheos31 To be honest I don't fully understand what you're referring to here? Could you explain it a bit more?

I'm currently looking at the tests and I found something that is probably not correct in this PR. I'm trying to understand how everything works together in order to fix the code and with that the code :)

Hello, I just said that NgMocks have to treat with the same maneer standalone components with "standalone: true" (old way) and without (Angular 19 way). NgMocks also have to treat the non standalone components differently.
The question is: how to differentiate these cases? Currently, as I could saw in the source code, I think NgMocks just cant. The only way to achieve the "isStandalone" check, IMO, is by knowing if a component is declared by a Module which NgMocks tried to mock before he tried to mock the component.

@NgModule({
    declarations: [
        NotStandaloneComponent,
    ],
    exports: [
        NotStandaloneComponent,
    ],
})
export class MyModule {}

It's just impossible to use a NotStandaloneComponent in another SComponent without this SComponent imports MyModule containing the NotStandaloneComponent. Dependency tree:

SComponent <--imports-- MyModule <--declares-- NotStandaloneComponent

Thus, using NgMocks, you'll necessarily call MockModule(MyModule) in the unit test of SComponent. Doing that giving us our entry point to the tree dependency which have to be mocked, and we therefore have the information we need to deduct our "isStandalone" check.

@Martinspire
Copy link

What if there was a parameter to make sure ngmocks knows something will not be a standalone component? We all probably know the component it is failing on and having MockModule(SomeModule, <some config>) or MockComponent(SomeComponent, <some config>) is fine by me. As long as I'm at least able to do such a thing. Its still much better than having to do a custom component just because ngmocks can't figure it out.

@FrankMerema
Copy link

FrankMerema commented Mar 7, 2025

In the ng-mocks-universe.ts there is a boolean value that checks if you're in the world of defaultStandalone AKA >= NG19 . We might be able to use this value to do the correct if/else right? In that case you don't have to do difficult checks :)

It will look like this if I'm correct:
image

@kheos31
Copy link

kheos31 commented Mar 7, 2025

In the ng-mocks-universe.ts there is a boolean value that checks if you're in the world of defaultStandalone AKA >= NG19 . We might be able to use this value to do the correct if/else right? In that case you don't have to do difficult checks :)

It will look like this if I'm correct: image

Unfortunately no, because external libraries could be "Angular 19" compatible but not migrated as "default standalone". It's the problem here, and why I suggested previous idea to manage this check.

@FrankMerema
Copy link

@KernelFolla I've pushed some of my changes here to this branch, maybe something you can take a look at as well: https://github.com/FrankMerema/ng-mocks/tree/feature/fix-pr-10826

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants