@memberjunction/ng-explorer-core 5.38.0 → 5.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +9 -0
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +5 -4
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +20 -9
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/single-record/single-record.component.d.ts +31 -75
- package/dist/lib/single-record/single-record.component.d.ts.map +1 -1
- package/dist/lib/single-record/single-record.component.js +60 -471
- package/dist/lib/single-record/single-record.component.js.map +1 -1
- package/package.json +46 -46
- package/dist/lib/__tests__/form-resolver.service.test.d.ts +0 -2
- package/dist/lib/__tests__/form-resolver.service.test.d.ts.map +0 -1
- package/dist/lib/__tests__/form-resolver.service.test.js +0 -258
- package/dist/lib/__tests__/form-resolver.service.test.js.map +0 -1
- package/dist/lib/services/form-resolver.service.d.ts +0 -139
- package/dist/lib/services/form-resolver.service.d.ts.map +0 -1
- package/dist/lib/services/form-resolver.service.js +0 -235
- package/dist/lib/services/form-resolver.service.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"single-record.component.js","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts","../../../src/lib/single-record/single-record.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAiB,iBAAiB,EAAE,SAAS,EAAgB,YAAY,EAAE,MAAM,EAAE,KAAK,EAAqB,MAAM,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE7J,OAAO,EAA0B,YAAY,EAA+B,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAElJ,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAiE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACxI,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;;;;;ICTnE,gCAAyD;;IAA7C,gCAAkB;;;IAS1B,4BAAuC;IAAA,YAAiB;IAAA,iBAAI;;;IAArB,cAAiB;IAAjB,wCAAiB;;;IAL1D,AADF,8BAA8C,aACL;IACrC,uBAAgD;IAClD,iBAAM;IACN,6BAAuC;IAAA,YAAgB;IAAA,iBAAK;IAC5D,mGAAmB;IAGnB,4BAAqC;IAAA,0DAA0C;IACjF,AADiF,iBAAI,EAC/E;;;IALmC,eAAgB;IAAhB,uCAAgB;IACvD,cAEC;IAFD,6CAEC;;;ADML,MAAM,OAAO,qBAAsB,SAAQ,oBAAoB;IAwDxC;IAvDiB,aAAa,CAAa;IAExD,WAAW,GAAiB,IAAI,YAAY,EAAE,CAAC;IACvD,IACW,UAAU,CAAC,KAAmB;QACvC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QAClE,IAAI,CAAC,WAAW,GAAG,KAAK,IAAI,IAAI,YAAY,EAAE,CAAC;QAC/C,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACvC,OAAO;QACT,CAAC;QACD,qFAAqF;QACrF,uFAAuF;QACvF,wFAAwF;QACxF,sFAAsF;QACtF,uFAAuF;QACvF,sFAAsF;QACtF,4FAA4F;QAC5F,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1F,OAAO;QACT,CAAC;QACD,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IACD,IAAW,UAAU;QACnB,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAEO,WAAW,GAAkB,EAAE,CAAC;IACxC,IACW,UAAU,CAAC,KAAoB;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,KAAK,KAAK,CAAC;QAC3C,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,IAAI,OAAO,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACrC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,IAAW,UAAU;QACnB,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAEe,eAAe,GAA4C,EAAE,CAAC;IAE7D,YAAY,GAAsB,IAAI,YAAY,EAAO,CAAC;IAC1D,WAAW,GAA6B,IAAI,YAAY,EAAc,CAAC;IACxF;;;OAGG;IACc,eAAe,GAAuB,IAAI,YAAY,EAAQ,CAAC;IAExE,mBAAmB,CAAsB;IACzC,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC9C,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IACtC,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAChC,YAAY,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAEnD,YAAqB,KAAqB;QACxC,KAAK,EAAE,CAAC;QADW,UAAK,GAAL,KAAK,CAAgB;QAExC,IAAI,CAAC,mBAAmB,GAAG,IAAI,mBAAmB,EAAE,CAAC;IACvD,CAAC;IAEM,cAAc,GAAW,EAAE,CAAA;IAC3B,cAAc,GAAY,KAAK,CAAC;IAChC,OAAO,GAAY,IAAI,CAAC;IACxB,UAAU,GAAkB,IAAI,CAAC;IACjC,WAAW,GAAkB,IAAI,CAAC;IAEzC,gEAAgE;IACxD,iBAAiB,GAA2C,IAAI,CAAC;IACjE,cAAc,GAAsB,IAAI,CAAC;IACzC,yBAAyB,GAAwB,IAAI,CAAC;IACtD,uBAAuB,GAAmB,EAAE,CAAC;IAC7C,gBAAgB,GAAG,KAAK,CAAC;IAEjC,QAAQ;IACR,CAAC;IAED,eAAe;QACb,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAU,IAAI,CAAC,WAAW,CAAC,CAAA;IAC3D,CAAC;IAED;;;;;;;;;;OAUG;IACK,iBAAiB;QACvB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;;;OAQG;IACK,kBAAkB,CAAC,CAAkC,EAAE,CAAkC;QAC/F,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACzB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACxF,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACxF,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;gBAAE,OAAO,KAAK,CAAC;YAC5D,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBAAE,OAAO,KAAK,CAAC;QAClF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACK,kBAAkB;QACxB,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAEhC,IAAI,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACnC,IAAI,CAAC,yBAAyB,CAAC,WAAW,EAAE,CAAC;YAC7C,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC;QACxC,CAAC;QAED,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC;gBAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;YAC9D,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAE3B,IAAI,IAAI,CAAC,aAAa,EAAE,gBAAgB,EAAE,CAAC;YACzC,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9C,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,UAAwB,EAAE,UAAkB;QAChE,uEAAuE;QACvE,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClD,kFAAkF;YAClF,2EAA2E;YAC3E,qEAAqE;YACrE,OAAO;QACT,CAAC;QAED,iFAAiF;QACjF,8EAA8E;QAC9E,EAAE;QACF,gFAAgF;QAChF,kFAAkF;QAClF,uFAAuF;QACvF,iFAAiF;QACjF,mFAAmF;QACnF,kFAAkF;QAClF,2EAA2E;QAC3E,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;QAC9B,IAAI,CAAC,WAAW,GAAG,UAAU,IAAI,IAAI,YAAY,EAAE,CAAC;QAEpD,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;QAC9B,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,WAAW,GAAG,MAAM,EAAE,iBAAiB,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;QAE9D,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC,iBAAiB,CACpB,sBAAsB,UAAU,GAAG,EACnC,4DAA4D,UAAU,gHAAgH,CACvL,CAAC;gBACF,OAAO;YACT,CAAC;YAED,2EAA2E;YAC3E,2DAA2D;YAC3D,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,oBAAoB,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAE5F,IAAI,UAAU,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC/B,IAAI,CAAC,iBAAiB,CACpB,8BAA8B,UAAU,IAAI,EAC5C,yFAAyF,UAAU,6RAA6R,UAAU,0EAA0E,EACpd,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,IAAI,QAAQ,EAAE,CACzE,CAAC;gBACF,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,eAAe,CAAa,UAAU,CAAC,CAAC;YAChE,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,gCAAgC,UAAU,6BAA6B,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAClH,CAAC;YAED,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;gBAClD,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,IAAI,CAAC,iBAAiB,CACpB,kBAAkB,UAAU,UAAU,EACtC,MAAM,CAAC,YAAY,EAAE,OAAO;wBAC1B,CAAC,CAAC,iBAAiB,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE;wBAChD,CAAC,CAAC,wCAAwC,UAAU,cAAc,UAAU,CAAC,QAAQ,EAAE,gHAAgH,EACzM,EAAE,SAAS,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,CACrC,CAAC;oBACF,OAAO;gBACT,CAAC;gBACD,+DAA+D;gBAC/D,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;YACvE,CAAC;iBACI,CAAC;gBACJ,MAAM,CAAC,SAAS,EAAE,CAAC;gBACnB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC;YAED,6DAA6D;YAC7D,IAAI,CAAC,yBAAyB,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC,SAA0B,EAAE,EAAE;gBAC1F,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM;oBAC3B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;YAEH,MAAM,gBAAgB,GAAG,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC;YAC7D,gBAAgB,CAAC,KAAK,EAAE,CAAC;YAEzB,yEAAyE;YACzE,uEAAuE;YACvE,oEAAoE;YACpE,MAAM,YAAY,GAAoC,UAAU,CAAC,IAAI,KAAK,aAAa;gBACrF,CAAC,CAAC,gBAAgB,CAAC,eAAe,CAAC,wBAAwB,CAAC;gBAC5D,CAAC,CAAC,gBAAgB,CAAC,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAE1D,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;gBACrC,YAAuD,CAAC,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;YAClH,CAAC;YAED,yCAAyC;YACzC,IAAI,CAAC,iBAAiB,GAAG,YAAY,CAAC;YACtC,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC;YAE7B,MAAM,QAAQ,GAAG,YAAY,CAAC,QAA6D,CAAC;YAC5F,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;YACzB,QAAQ,CAAC,eAAe,GAAG,WAAW,CAAC;YACvC,QAAQ,CAAC,QAAQ,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,2CAA2C;YAErF,4DAA4D;YAC5D,+DAA+D;YAC/D,kEAAkE;YAClE,iEAAiE;YACjE,gEAAgE;YAChE,+DAA+D;YAC/D,2DAA2D;YAC3D,+DAA+D;YAC/D,4DAA4D;YAC5D,4DAA4D;YAC5D,EAAE;YACF,+DAA+D;YAC/D,iEAAiE;YACjE,QAAQ,CAAC,QAAQ,GAAG,CAAC,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC;iBAC5C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC;iBAClC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACT,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,KAAK,EAAE,CAAC,CAAC,IAAI,IAAI,YAAY,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;gBACnD,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,MAAM,EAAE,CAAC,CAAC,MAAM;aACjB,CAAC,CAAC,CAAC;YACN,QAAQ,CAAC,gBAAgB,GAAG,UAAU,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAC9F,qEAAqE;YACrE,sEAAsE;YACtE,sEAAsE;YACtE,QAAQ,CAAC,gBAAgB,GAAG,CAAC,SAAwB,EAAE,EAAE;gBACvD,8DAA8D;gBAC9D,gEAAgE;gBAChE,iEAAiE;gBACjE,mEAAmE;gBACnE,mEAAmE;gBACnE,0DAA0D;gBAC1D,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBACvB,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;gBACnD,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;gBAC9D,CAAC;gBACD,mEAAmE;gBACnE,mCAAmC;gBACnC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAC7C,CAAC,CAAC;YAEF,qEAAqE;YACrE,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;YAErC,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;YAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,IAAI,CAAC,iBAAiB,CACpB,kBAAkB,UAAU,UAAU,EACtC,2DAA2D,GAAG,EAAE,EAChE,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACK,iBAAiB,CAAC,KAAa,EAAE,MAAc,EAAE,OAAiC;QACxF,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QAErB,yEAAyE;QACzE,OAAO,CAAC,KAAK,CACX,kBAAkB,KAAK,KAAK,MAAM,EAAE;YAClC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpE,CAAC;QAEF,gEAAgE;QAChE,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC;gBAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;YAC9D,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,EAAE,gBAAgB,EAAE,CAAC;YACzC,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9C,CAAC;QAED,4BAA4B;QAC5B,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC3B,CAAC;IAES,kBAAkB,CAAC,MAAkB;QAC7C,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,sDAAsD;QACtD,IAAI,OAAO,IAAI,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;YAC7C,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtC,OAAO;YACT,CAAC;YACD,sEAAsE;YACtE,MAAM,EAAE,GAAG,IAAI,oBAAoB,EAAE,CAAC;YACtC,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAClD,qCAAqC;YACrC,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;gBAC1F,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;gBACtG,IAAI,CAAC,EAAE,CAAC;oBACN,8EAA8E;oBAC9E,QAAQ,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;wBACjC,KAAK,iBAAiB,CAAC,MAAM;4BAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;4BACrC,MAAM;wBACR,KAAK,iBAAiB,CAAC,MAAM;4BAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;4BACjD,MAAM;wBACR,KAAK,iBAAiB,CAAC,OAAO;4BAC5B,IAAI,GAAG,CAAC,KAAK,KAAK,OAAO,IAAI,GAAG,CAAC,KAAK,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;gCACxF,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;;gCAEjC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;4BAClC,MAAM;wBACR,KAAK,iBAAiB,CAAC,IAAI;4BACzB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;4BAC/C,MAAM;oBACV,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;aACI,CAAC;YACJ,oEAAoE;YACpE,MAAM,YAAY,GAAG,IAAI,CAAC,eAA0C,CAAC;YACrE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;iBACtB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;iBAC5E,OAAO,CAAC,GAAG,CAAC,EAAE;gBACb,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;gBAC5F,IAAI,CAAC,EAAE,CAAC;oBACN,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;oBAChC,4CAA4C;oBAC5C,QAAQ,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;wBACjC,KAAK,iBAAiB,CAAC,MAAM;4BAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;4BACzC,MAAM;wBACR,KAAK,iBAAiB,CAAC,MAAM;4BAC3B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;4BAC1F,MAAM;wBACR,KAAK,iBAAiB,CAAC,OAAO;4BAC5B,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;gCAC/B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;4BACzB,CAAC;iCACI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gCACnC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;4BACjF,CAAC;iCACI,CAAC;gCACJ,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;4BAC3B,CAAC;4BACD,MAAM;wBACR,KAAK,iBAAiB,CAAC,IAAI;4BACzB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;4BACnF,MAAM;wBACR;4BACE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;4BACvB,MAAM;oBACV,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACP,CAAC;IACH,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,IAAuB;QACnD,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAEhC,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAC/B,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,KAA0B,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EACrF,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,KAA4B,EAAE,EAAE;YAC3D,IAAI,CAAC,aAAa,CAAC,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzF,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAEO,gBAAgB,CAAC,KAA0B;QACjD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,QAAQ;gBACX,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;gBACjH,MAAM;YACR,KAAK,YAAY;gBACf,4EAA4E;gBAC5E,wEAAwE;gBACxE,yEAAyE;gBACzE,qEAAqE;gBACrE,wEAAwE;gBACxE,+CAA+C;gBAC/C,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,KAAK,CAAC,UAAU,EAAE;oBAC3D,eAAe,EAAE,KAAK,CAAC,aAAa;oBACpC,WAAW,EAAE,IAAI;iBAClB,CAAC,CAAC;gBACH,MAAM;YACR,KAAK,kBAAkB;gBACrB,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC5E,MAAM;YACR,KAAK,eAAe;gBAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBACjC,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;gBACrD,MAAM;YACR,KAAK,SAAS;gBACZ,kEAAkE;gBAClE,kEAAkE;gBAClE,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;gBAC5B,MAAM;QACV,CAAC;IACH,CAAC;IAEO,wBAAwB;QAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC/C,GAAG,CAAC,WAAW,EAAE,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,uBAAuB,GAAG,EAAE,CAAC;IACpC,CAAC;IAED,WAAW;QACT,oDAAoD;QACpD,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAEhC,qFAAqF;QACrF,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,mEAAmE;QACnE,IAAI,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACnC,IAAI,CAAC,yBAAyB,CAAC,WAAW,EAAE,CAAC;YAC7C,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC;QACxC,CAAC;QAED,4BAA4B;QAC5B,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;QAED,6DAA6D;QAC7D,IAAI,IAAI,CAAC,aAAa,EAAE,gBAAgB,EAAE,CAAC;YACzC,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9C,CAAC;QAED,cAAc;QACd,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;IAC9B,CAAC;+GAxfU,qBAAqB;6DAArB,qBAAqB;2BACrB,SAAS;;;;;YClBtB,8FAAe;YAGf,uFAAkB;YAYlB,sFAAyB;;YAfzB,sCAEC;YACD,cAWC;YAXD,yCAWC;;;iFDGY,qBAAqB;cANjC,SAAS;6BACI,KAAK,YACP,kBAAkB;;kBAK3B,SAAS;mBAAC,SAAS,EAAE,EAAC,MAAM,EAAE,IAAI,EAAC;;kBAGnC,KAAK;;kBAwBL,KAAK;;kBAYL,KAAK;;kBAEL,MAAM;;kBACN,MAAM;;kBAKN,MAAM;;kFAhDI,qBAAqB","sourcesContent":["import { AfterViewInit, ChangeDetectorRef, Component, ComponentRef, EventEmitter, inject, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router'\nimport { Metadata, KeyValuePair, CompositeKey, BaseEntity, BaseEntityEvent, FieldValueCollection, EntityFieldTSType } from '@memberjunction/core';\nimport { Subscription } from 'rxjs';\nimport { Container } from '@memberjunction/ng-container-directives';\nimport { BaseFormComponent, FormNavigationEvent, FormNotificationEvent, InteractiveFormComponent } from '@memberjunction/ng-base-forms';\nimport { NavigationService, RecentAccessService, SharedService } from '@memberjunction/ng-shared';\nimport { FormResolverService } from '../services/form-resolver.service';\n\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n@Component({\n standalone: false,\n selector: 'mj-single-record',\n templateUrl: './single-record.component.html',\n styleUrls: ['./single-record.component.css']\n})\nexport class SingleRecordComponent extends BaseAngularComponent implements OnInit, AfterViewInit, OnDestroy {\n @ViewChild(Container, {static: true}) formContainer!: Container;\n\n private _primaryKey: CompositeKey = new CompositeKey();\n @Input()\n public set PrimaryKey(value: CompositeKey) {\n const changed = !this.compositeKeysEqual(this._primaryKey, value);\n this._primaryKey = value ?? new CompositeKey();\n if (!changed || !this._viewInitialized) {\n return;\n }\n // Skip reload when the incoming key just reflects the current record's now-saved PK.\n // After a new-record save, BaseResourceComponent updates parent.Data.ResourceRecordID,\n // the parent's PrimaryKey getter then returns a fresh CK with the saved ID, and Angular\n // pushes it down to us. The form already represents that record in-place — destroying\n // and rebuilding it here would lose edit state, refetch from DB, and spawn a redundant\n // GetRecordFavoriteStatus call. Genuine external swaps (parent rebinds to a different\n // record) still trigger the reload because `value` won't match `_currentRecord.PrimaryKey`.\n if (this._currentRecord && this.compositeKeysEqual(value, this._currentRecord.PrimaryKey)) {\n return;\n }\n this.reloadCurrentForm();\n }\n public get PrimaryKey(): CompositeKey {\n return this._primaryKey;\n }\n\n private _entityName: string | null = '';\n @Input()\n public set entityName(value: string | null) {\n const changed = this._entityName !== value;\n this._entityName = value;\n if (changed && this._viewInitialized) {\n this.reloadCurrentForm();\n }\n }\n public get entityName(): string | null {\n return this._entityName;\n }\n\n @Input() public newRecordValues: string | Record<string, unknown> | null = '';\n\n @Output() public loadComplete: EventEmitter<any> = new EventEmitter<any>();\n @Output() public recordSaved: EventEmitter<BaseEntity> = new EventEmitter<BaseEntity>();\n /**\n * Emitted when the hosted form asks to be dismissed (e.g., user clicked Discard on\n * a brand-new record). Parent components should close the tab / route the user back.\n */\n @Output() public recordDismissed: EventEmitter<void> = new EventEmitter<void>();\n\n private recentAccessService: RecentAccessService;\n private navigationService = inject(NavigationService);\n private sharedService = inject(SharedService);\n private cdr = inject(ChangeDetectorRef);\n private formResolver = inject(FormResolverService);\n\n constructor (private route: ActivatedRoute) {\n super();\n this.recentAccessService = new RecentAccessService();\n }\n\n public appDescription: string = ''\n public useGenericForm: boolean = false;\n public loading: boolean = true;\n public errorTitle: string | null = null;\n public errorDetail: string | null = null;\n\n // Track dynamically created components and entities for cleanup\n private _formComponentRef: ComponentRef<BaseFormComponent> | null = null;\n private _currentRecord: BaseEntity | null = null;\n private _eventHandlerSubscription: Subscription | null = null;\n private _formEventSubscriptions: Subscription[] = [];\n private _viewInitialized = false;\n\n ngOnInit(): void {\n }\n\n ngAfterViewInit() {\n this._viewInitialized = true;\n this.LoadForm(this._primaryKey, <string>this._entityName)\n }\n\n /**\n * Re-run LoadForm when an @Input changes after initial view init.\n *\n * Defense-in-depth: the tab/cache rekey on save (in TabContainerComponent) is the\n * primary fix that prevents stale-form bugs after creating a new record. This setter\n * path ensures we self-heal if a host ever swaps the bound inputs without recreating\n * the component — without it, LoadForm only ever runs once per component instance.\n *\n * Tears down the previous form (component, record, event handlers) before loading the\n * new one so we don't leak references or stack multiple forms in the container.\n */\n private reloadCurrentForm(): void {\n this.teardownActiveForm();\n if (!this._entityName) {\n return;\n }\n this.loading = true;\n this.LoadForm(this._primaryKey, this._entityName);\n }\n\n /**\n * Compare two CompositeKey instances by their key/value contents.\n * Two different instances representing the same key should NOT count as a change.\n *\n * Filters out KVPs with empty Values before comparing. Different callers represent\n * \"no key\" differently — some use an empty KeyValuePairs list, others use a single\n * KVP with an empty Value (e.g. `LoadFromURLSegment(entity, '')`). Both mean the\n * same thing semantically and must not trigger a change.\n */\n private compositeKeysEqual(a: CompositeKey | null | undefined, b: CompositeKey | null | undefined): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n const aKvps = (a.KeyValuePairs || []).filter(kvp => String(kvp.Value ?? '').length > 0);\n const bKvps = (b.KeyValuePairs || []).filter(kvp => String(kvp.Value ?? '').length > 0);\n if (aKvps.length !== bKvps.length) return false;\n for (let i = 0; i < aKvps.length; i++) {\n if (aKvps[i].FieldName !== bKvps[i].FieldName) return false;\n if (String(aKvps[i].Value ?? '') !== String(bKvps[i].Value ?? '')) return false;\n }\n return true;\n }\n\n /**\n * Tear down the currently rendered form so a new one can be loaded in its place.\n * Mirrors the cleanup done in ngOnDestroy, minus the final state reset.\n */\n private teardownActiveForm(): void {\n this.cleanupFormSubscriptions();\n\n if (this._eventHandlerSubscription) {\n this._eventHandlerSubscription.unsubscribe();\n this._eventHandlerSubscription = null;\n }\n\n if (this._formComponentRef) {\n try { this._formComponentRef.destroy(); } catch { /* noop */ }\n this._formComponentRef = null;\n }\n\n this._currentRecord = null;\n\n if (this.formContainer?.viewContainerRef) {\n this.formContainer.viewContainerRef.clear();\n }\n }\n\n public async LoadForm(primaryKey: CompositeKey, entityName: string) {\n // Perform any necessary actions with the ViewID, such as fetching data\n if (!entityName || entityName.trim().length === 0) {\n // No entity yet — caller will re-invoke once it has one. Don't emit loadComplete;\n // it would race the real load. The shell's recovery timer will surface the\n // \"taking longer than expected\" reset if this is the terminal state.\n return;\n }\n\n // Write through to backing fields directly. Going through the setters here would\n // re-trigger reloadCurrentForm() and recurse — we're already inside LoadForm.\n //\n // Use the parameter as-is rather than substituting `new CompositeKey()` for the\n // !HasValue branch. The parent's getter (e.g. EntityRecordResource.GetPrimaryKey)\n // builds its CK via `LoadFromURLSegment(entity, '')`, which yields `[{FieldName: 'ID',\n // Value: ''}]` for a new record — a single KVP with an empty value, NOT an empty\n // KVP list. Substituting an empty CK here creates a structural mismatch that makes\n // the setter's compositeKeysEqual see a phantom change on the very next CD cycle,\n // which triggers reload → LoadForm → CD → setter → reload (infinite loop).\n this._entityName = entityName;\n this._primaryKey = primaryKey ?? new CompositeKey();\n\n const md = this.ProviderToUse;\n const entity = md.EntityByName(entityName);\n const permissions = entity?.GetUserPermisions(md.CurrentUser);\n\n try {\n if (!entity) {\n this.failWithUserError(\n `Entity not found: \"${entityName}\"`,\n `This MemberJunction instance has no metadata for entity \"${entityName}\". Check that the entity name in the URL matches a row in __mj.Entity, and that the metadata cache is current.`\n );\n return;\n }\n\n // Resolve which form to render: User/Role/Global EntityFormOverride first,\n // then ClassFactory-registered Angular form, then nothing.\n const resolution = await this.formResolver.ResolveFormForEntity(entity, md.CurrentUser, md);\n\n if (resolution.kind === 'none') {\n this.failWithUserError(\n `No form is registered for \"${entityName}\".`,\n `MemberJunction could not find an EntityFormOverride or a class-based form for entity \"${entityName}\". This usually means CodeGen has not generated a form for this entity in the running build (forms live under packages/MJExplorer or your app's entity-form package). Run CodeGen, ensure the generated module is imported, register a custom form via @RegisterClass(BaseFormComponent, '${entityName}'), or create an EntityFormOverride row pointing at a runtime Component.`,\n { entityId: entity.ID, recordKey: primaryKey?.ToString?.() ?? '(none)' }\n );\n return;\n }\n\n const record = await md.GetEntityObject<BaseEntity>(entityName);\n if (!record) {\n throw new Error(`Unable to instantiate entity ${entityName} with primary key values: ${primaryKey.ToString()}`);\n }\n\n if (primaryKey.HasValue) {\n const loadOk = await record.InnerLoad(primaryKey);\n if (!loadOk) {\n this.failWithUserError(\n `Could not load ${entityName} record.`,\n record.LatestResult?.Message\n ? `Server error: ${record.LatestResult.Message}`\n : `InnerLoad returned false for entity \"${entityName}\" with key ${primaryKey.ToString()}. The record may not exist, you may lack permission to view it, or the load may have been blocked server-side.`,\n { recordKey: primaryKey.ToString() }\n );\n return;\n }\n // Log access to existing record (fire-and-forget, don't await)\n this.recentAccessService.logAccess(entityName, primaryKey, 'record');\n }\n else {\n record.NewRecord();\n this.SetNewRecordValues(record);\n }\n\n // CRITICAL: Track the event handler subscription for cleanup\n this._eventHandlerSubscription = record.RegisterEventHandler((eventType: BaseEntityEvent) => {\n if (eventType.type === 'save')\n this.recordSaved.emit(record);\n });\n\n const viewContainerRef = this.formContainer.viewContainerRef;\n viewContainerRef.clear();\n\n // Generated forms expose properties (e.g. `userPermissions`) that aren't\n // on the abstract `BaseFormComponent`. Widen the instance type for the\n // setter surface we share across class-based and interactive forms.\n const componentRef: ComponentRef<BaseFormComponent> = resolution.kind === 'interactive'\n ? viewContainerRef.createComponent(InteractiveFormComponent)\n : viewContainerRef.createComponent(resolution.subClass);\n\n if (resolution.kind === 'interactive') {\n (componentRef as ComponentRef<InteractiveFormComponent>).instance.ComponentID = resolution.override.ComponentID;\n }\n\n // Track component and record for cleanup\n this._formComponentRef = componentRef;\n this._currentRecord = record;\n\n const instance = componentRef.instance as BaseFormComponent & { userPermissions?: unknown };\n instance.record = record;\n instance.userPermissions = permissions;\n instance.EditMode = !primaryKey.HasValue; // for new records go direct into edit mode\n\n // Push variant list + active selection into the form so the\n // record-form-container's picker renders. The resolver returns\n // every applicable override regardless of status, but the runtime\n // picker should only surface **Active** ones — Inactive rows are\n // historical (e.g. the previous Component version that an agent\n // refinement superseded) and Pending rows are AI-authored work\n // awaiting activation in Form Builder. Picking either does\n // nothing at runtime (pickActive requires Status='Active'), so\n // including them in the picker was misleading the user into\n // thinking \"I can switch to this\" when they actually can't.\n //\n // Authorship of Pending/Inactive overrides happens in the Form\n // Builder cockpit, which intentionally shows the full lifecycle.\n instance.Variants = (resolution.variants ?? [])\n .filter(v => v.Status === 'Active')\n .map(v => ({\n ID: v.ID,\n Label: v.Name ?? `Override ${v.ID.substring(0, 8)}`,\n Scope: v.Scope,\n Status: v.Status,\n }));\n instance.CurrentVariantID = resolution.kind === 'interactive' ? resolution.override.ID : null;\n // Wire the handler: persist the selection in localStorage and reload\n // the form. Reload uses the existing entry path so all the resolver's\n // tier/priority semantics apply (and the saved choice now overrides).\n instance.OnVariantChanged = (variantID: string | null) => {\n // null from the picker = user picked the \"Default form\" row →\n // store the explicit-default sentinel so the resolver skips ALL\n // overrides and falls back to the CodeGen / @RegisterClass form.\n // Without this, clearing the preference let the resolver auto-pick\n // the first Active override again, making Default unreachable from\n // the UI for entities that have any user-scope overrides.\n if (variantID === null) {\n this.formResolver.SetExplicitDefault(entityName);\n } else {\n this.formResolver.SetSelectedVariant(entityName, variantID);\n }\n // Re-run the load with the same key — the resolver will honour the\n // updated session-local selection.\n this.LoadForm(this.PrimaryKey, entityName);\n };\n\n // Subscribe to form @Output events and map them to Explorer services\n this.subscribeToFormEvents(instance);\n\n this.useGenericForm = false;\n this.errorTitle = null;\n this.errorDetail = null;\n this.loadComplete.emit();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n this.failWithUserError(\n `Failed to load ${entityName} record.`,\n `An unexpected error occurred while loading this record: ${msg}`,\n { error: err }\n );\n }\n\n this.loading = false;\n this.cdr.detectChanges();\n }\n\n /**\n * Render a user-visible error state inside the record pane AND log a structured\n * console.error for developers. Always emits `loadComplete` so the Explorer shell\n * does not hang on its first-resource-load gate.\n */\n private failWithUserError(title: string, detail: string, context?: Record<string, unknown>): void {\n this.errorTitle = title;\n this.errorDetail = detail;\n this.loading = false;\n\n // Single structured console.error for devs — easy to grep, easy to read.\n console.error(\n `[SingleRecord] ${title}\\n${detail}` +\n (context ? `\\nContext: ${JSON.stringify(context, null, 2)}` : '')\n );\n\n // Clear any prior form/component so the error UI is what shows.\n if (this._formComponentRef) {\n try { this._formComponentRef.destroy(); } catch { /* noop */ }\n this._formComponentRef = null;\n }\n if (this.formContainer?.viewContainerRef) {\n this.formContainer.viewContainerRef.clear();\n }\n\n // Always unblock the shell.\n this.loadComplete.emit();\n this.cdr.detectChanges();\n }\n\n protected SetNewRecordValues(record: BaseEntity) {\n if (!this.newRecordValues) {\n return;\n }\n\n // Handle both object and string (URL segment) formats\n if (typeof this.newRecordValues === 'string') {\n if (this.newRecordValues.length === 0) {\n return;\n }\n // we have a URL segment string format: \"field1|value1||field2|value2\"\n const fv = new FieldValueCollection();\n fv.SimpleLoadFromURLSegment(this.newRecordValues);\n // now apply the values to the record\n fv.KeyValuePairs.filter(kvp => kvp.Value !== null && kvp.Value !== undefined).forEach(kvp => {\n const f = record.Fields.find(f => f.Name.trim().toLowerCase() === kvp.FieldName.trim().toLowerCase());\n if (f) {\n // make sure we set the value to the right type based on the f.TSType property\n switch (f.EntityFieldInfo.TSType) {\n case EntityFieldTSType.String:\n record.Set(kvp.FieldName, kvp.Value);\n break;\n case EntityFieldTSType.Number:\n record.Set(kvp.FieldName, parseFloat(kvp.Value));\n break;\n case EntityFieldTSType.Boolean:\n if (kvp.Value === 'false' || kvp.Value === '0' || kvp.Value.toString().trim().length === 0 )\n record.Set(kvp.FieldName, false);\n else\n record.Set(kvp.FieldName, true);\n break;\n case EntityFieldTSType.Date:\n record.Set(kvp.FieldName, new Date(kvp.Value));\n break;\n }\n }\n });\n }\n else {\n // we have a plain object format: { field1: value1, field2: value2 }\n const recordValues = this.newRecordValues as Record<string, unknown>;\n Object.keys(recordValues)\n .filter(key => recordValues[key] !== null && recordValues[key] !== undefined)\n .forEach(key => {\n const f = record.Fields.find(f => f.Name.trim().toLowerCase() === key.trim().toLowerCase());\n if (f) {\n const value = recordValues[key];\n // Set the value with proper type conversion\n switch (f.EntityFieldInfo.TSType) {\n case EntityFieldTSType.String:\n record.Set(key, value?.toString() || '');\n break;\n case EntityFieldTSType.Number:\n record.Set(key, typeof value === 'number' ? value : parseFloat(value?.toString() || '0'));\n break;\n case EntityFieldTSType.Boolean:\n if (typeof value === 'boolean') {\n record.Set(key, value);\n }\n else if (typeof value === 'string') {\n record.Set(key, value !== 'false' && value !== '0' && value.trim().length > 0);\n }\n else {\n record.Set(key, !!value);\n }\n break;\n case EntityFieldTSType.Date:\n record.Set(key, value instanceof Date ? value : new Date(value?.toString() || ''));\n break;\n default:\n record.Set(key, value);\n break;\n }\n }\n });\n }\n }\n\n /**\n * Subscribe to BaseFormComponent @Output events and map them to Explorer services.\n */\n private subscribeToFormEvents(form: BaseFormComponent): void {\n this.cleanupFormSubscriptions();\n\n this._formEventSubscriptions.push(\n form.Navigate.subscribe((event: FormNavigationEvent) => this.handleNavigation(event)),\n form.Notification.subscribe((event: FormNotificationEvent) => {\n this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);\n })\n );\n }\n\n private handleNavigation(event: FormNavigationEvent): void {\n switch (event.Kind) {\n case 'record':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });\n break;\n case 'new-record':\n // Creating a new related record from inside an open record form (e.g. + New\n // on a related-entity grid). Force a new tab so the parent record stays\n // intact — otherwise the new-record form silently replaces the parent in\n // single-resource mode and the user loses their context. This is the\n // original intent of dea32401ff, now stated explicitly at the call site\n // instead of as a global navigation heuristic.\n this.navigationService.OpenNewEntityRecord(event.EntityName, {\n newRecordValues: event.DefaultValues,\n forceNewTab: true,\n });\n break;\n case 'entity-hierarchy':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey);\n break;\n case 'external-link':\n window.open(event.Url, '_blank');\n break;\n case 'email':\n window.open(`mailto:${event.EmailAddress}`, '_self');\n break;\n case 'dismiss':\n // Form asked to be dismissed (typically Discard on a new record).\n // Re-emit so the parent (EntityRecordResource) can close the tab.\n this.recordDismissed.emit();\n break;\n }\n }\n\n private cleanupFormSubscriptions(): void {\n for (const sub of this._formEventSubscriptions) {\n sub.unsubscribe();\n }\n this._formEventSubscriptions = [];\n }\n\n ngOnDestroy(): void {\n // CRITICAL: Clean up form event subscriptions first\n this.cleanupFormSubscriptions();\n\n // CRITICAL: Clean up dynamically created form component to prevent zombie components\n if (this._formComponentRef) {\n this._formComponentRef.destroy();\n this._formComponentRef = null;\n }\n\n // CRITICAL: Unsubscribe from event handler to prevent memory leaks\n if (this._eventHandlerSubscription) {\n this._eventHandlerSubscription.unsubscribe();\n this._eventHandlerSubscription = null;\n }\n \n // Clean up record reference\n if (this._currentRecord) {\n this._currentRecord = null;\n }\n \n // Clear the view container to ensure no lingering references\n if (this.formContainer?.viewContainerRef) {\n this.formContainer.viewContainerRef.clear();\n }\n \n // Reset state\n this.loading = true;\n this.useGenericForm = false;\n }\n}\n","@if (loading) {\n <mj-loading [showText]=\"false\" size=\"large\"></mj-loading>\n}\n@if (errorTitle) {\n <div class=\"single-record-error\" role=\"alert\">\n <div class=\"single-record-error__icon\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n </div>\n <h2 class=\"single-record-error__title\">{{ errorTitle }}</h2>\n @if (errorDetail) {\n <p class=\"single-record-error__detail\">{{ errorDetail }}</p>\n }\n <p class=\"single-record-error__hint\">See browser console for technical details.</p>\n </div>\n}\n<ng-template mjContainer></ng-template>\n"]}
|
|
1
|
+
{"version":3,"file":"single-record.component.js","sourceRoot":"","sources":["../../../src/lib/single-record/single-record.component.ts","../../../src/lib/single-record/single-record.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAc,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAA8C,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACnH,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;;AAErE;;;;;;;;;;;;;GAaG;AAOH,MAAM,OAAO,qBAAsB,SAAQ,oBAAoB;IAC7C,UAAU,GAAiB,IAAI,YAAY,EAAE,CAAC;IAC9C,UAAU,GAAkB,EAAE,CAAC;IAC/B,eAAe,GAA4C,EAAE,CAAC;IAE7D,YAAY,GAAuB,IAAI,YAAY,EAAQ,CAAC;IAC5D,WAAW,GAA6B,IAAI,YAAY,EAAc,CAAC;IACxF,wFAAwF;IACvE,eAAe,GAAuB,IAAI,YAAY,EAAQ,CAAC;IAExE,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC9C,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IACtC,aAAa,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAC/C,mBAAmB,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAExD,uEAAuE;IACvE,cAAc;QACZ,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,uEAAuE;IACvE,aAAa,CAAC,MAAkB;QAC9B,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;IAED,OAAO,CAAC,MAAkB;QACxB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,cAAc,CAAC,KAA4B;QACzC,IAAI,CAAC,aAAa,CAAC,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzF,CAAC;IAED,4EAA4E;IAC5E,gBAAgB,CAAC,KAA0B;QACzC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,QAAQ;gBACX,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;gBACjH,MAAM;YACR,KAAK,YAAY;gBACf,6EAA6E;gBAC7E,sDAAsD;gBACtD,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,KAAK,CAAC,UAAU,EAAE;oBAC3D,eAAe,EAAE,KAAK,CAAC,aAAa;oBACpC,WAAW,EAAE,IAAI;iBAClB,CAAC,CAAC;gBACH,MAAM;YACR,KAAK,kBAAkB;gBACrB,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC5E,MAAM;YACR,KAAK,eAAe;gBAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBACjC,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;gBACrD,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;gBAC5B,MAAM;YACR,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACtB,gFAAgF;gBAChF,iFAAiF;gBACjF,iBAAiB;gBACjB,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;oBAClC,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,QAAQ;oBAC5C,eAAe,EAAE,KAAK,CAAC,eAAe;oBACtC,QAAQ,EAAE,KAAK,CAAC,QAAQ;iBACzB,CAAC,CAAC;gBACH,GAAG,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC1D,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;iQA3EU,qBAAqB,yBAArB,qBAAqB;6DAArB,qBAAqB;YC1BlC,8CAWuC;YAArC,AADA,AADA,AADA,AADA,AADA,AADA,6HAAgB,oBAAgB,IAAC,0GACpB,oBAAgB,IAAC,oHACf,yBAAqB,IAAC,wGAC5B,mBAAe,IAAC,8GACb,4BAAwB,IAAC,sHACrB,0BAAsB,IAAC,0GAC1B,0BAAsB,IAAC;YACtC,iBAAsB;;YARpB,AADA,AADA,AADA,2CAAyB,8BACA,wCACU,0BACd;;;iFDsBV,qBAAqB;cANjC,SAAS;6BACI,KAAK,YACP,kBAAkB;;kBAK3B,KAAK;;kBACL,KAAK;;kBACL,KAAK;;kBAEL,MAAM;;kBACN,MAAM;;kBAEN,MAAM;;kFARI,qBAAqB","sourcesContent":["import { Component, EventEmitter, inject, Input, Output } from '@angular/core';\nimport { CompositeKey, BaseEntity } from '@memberjunction/core';\nimport { FormNavigationEvent, FormNotificationEvent, MJFormPresenterService } from '@memberjunction/ng-base-forms';\nimport { NavigationService, RecentAccessService, SharedService } from '@memberjunction/ng-shared';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n\n/**\n * Explorer-side host for a single entity record in the main tab area.\n *\n * This is now a **thin wrapper** around the Generic `<mj-entity-form-host>`\n * (in `@memberjunction/ng-base-forms`), which owns all the mechanics: resolving\n * the form (class / custom / interactive override + variants), loading the\n * record, dynamically creating the form, binding it, and tearing it down.\n *\n * SingleRecordComponent's only remaining job is the **Explorer mapping**:\n * translating the host's framework-agnostic events into Explorer services —\n * `Navigate` → {@link NavigationService}, `Notification` → {@link SharedService},\n * record loads → {@link RecentAccessService} — none of which belong in a Generic\n * component.\n */\n@Component({\n standalone: false,\n selector: 'mj-single-record',\n templateUrl: './single-record.component.html',\n styleUrls: ['./single-record.component.css']\n})\nexport class SingleRecordComponent extends BaseAngularComponent {\n @Input() public PrimaryKey: CompositeKey = new CompositeKey();\n @Input() public entityName: string | null = '';\n @Input() public newRecordValues: string | Record<string, unknown> | null = '';\n\n @Output() public loadComplete: EventEmitter<void> = new EventEmitter<void>();\n @Output() public recordSaved: EventEmitter<BaseEntity> = new EventEmitter<BaseEntity>();\n /** Emitted when the hosted form asks to be dismissed (e.g. Discard on a new record). */\n @Output() public recordDismissed: EventEmitter<void> = new EventEmitter<void>();\n\n private navigationService = inject(NavigationService);\n private sharedService = inject(SharedService);\n private formPresenter = inject(MJFormPresenterService);\n private recentAccessService = new RecentAccessService();\n\n /** Unblock the shell's first-resource-load gate (success or error). */\n onLoadComplete(): void {\n this.loadComplete.emit();\n }\n\n /** Log access for existing records once the form's record is ready. */\n onRecordReady(record: BaseEntity): void {\n if (record?.IsSaved) {\n this.recentAccessService.logAccess(record.EntityInfo.Name, record.PrimaryKey, 'record');\n }\n }\n\n onSaved(record: BaseEntity): void {\n this.recordSaved.emit(record);\n }\n\n onNotification(event: FormNotificationEvent): void {\n this.sharedService.CreateSimpleNotification(event.Message, event.Type, event.Duration);\n }\n\n /** Map the form's navigation requests onto Explorer's NavigationService. */\n handleNavigation(event: FormNavigationEvent): void {\n switch (event.Kind) {\n case 'record':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey, { forceNewTab: event.OpenInNewTab });\n break;\n case 'new-record':\n // Creating a related record from inside an open form: force a new tab so the\n // parent record stays intact in single-resource mode.\n this.navigationService.OpenNewEntityRecord(event.EntityName, {\n newRecordValues: event.DefaultValues,\n forceNewTab: true,\n });\n break;\n case 'entity-hierarchy':\n this.navigationService.OpenEntityRecord(event.EntityName, event.PrimaryKey);\n break;\n case 'external-link':\n window.open(event.Url, '_blank');\n break;\n case 'email':\n window.open(`mailto:${event.EmailAddress}`, '_self');\n break;\n case 'dismiss':\n this.recordDismissed.emit();\n break;\n case 'create-related': {\n // A FK field wants a new related record created. Open the related entity's form\n // as a dialog/slide-in (prefilled), then hand the saved record back so the field\n // can select it.\n const ref = this.formPresenter.Open({\n EntityName: event.EntityName,\n Presentation: event.Presentation ?? 'dialog',\n NewRecordValues: event.NewRecordValues,\n Provider: event.Provider,\n });\n ref.AfterSaved().then(created => event.Complete(created));\n break;\n }\n }\n }\n}\n","<mj-entity-form-host\n [EntityName]=\"entityName\"\n [PrimaryKey]=\"PrimaryKey\"\n [NewRecordValues]=\"newRecordValues\"\n [Provider]=\"Provider\"\n (LoadComplete)=\"onLoadComplete()\"\n (LoadError)=\"onLoadComplete()\"\n (RecordReady)=\"onRecordReady($event)\"\n (Saved)=\"onSaved($event)\"\n (Navigate)=\"handleNavigation($event)\"\n (Notification)=\"onNotification($event)\"\n (Dismissed)=\"recordDismissed.emit()\">\n</mj-entity-form-host>\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-explorer-core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.39.0",
|
|
4
4
|
"description": "MemberJunction Explorer: Core Angular Components",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -32,51 +32,51 @@
|
|
|
32
32
|
"@angular/cdk": "21.1.3",
|
|
33
33
|
"@angular/platform-browser": "21.1.3",
|
|
34
34
|
"@auth0/auth0-angular": "^2.6.0",
|
|
35
|
-
"@memberjunction/ai-core-plus": "5.
|
|
36
|
-
"@memberjunction/ai-engine-base": "5.
|
|
37
|
-
"@memberjunction/communication-types": "5.
|
|
38
|
-
"@memberjunction/core": "5.
|
|
39
|
-
"@memberjunction/core-entities": "5.
|
|
40
|
-
"@memberjunction/entity-communications-client": "5.
|
|
41
|
-
"@memberjunction/export-engine": "5.
|
|
42
|
-
"@memberjunction/global": "5.
|
|
43
|
-
"@memberjunction/graphql-dataprovider": "5.
|
|
44
|
-
"@memberjunction/interactive-component-types": "5.
|
|
45
|
-
"@memberjunction/lists-base": "5.
|
|
46
|
-
"@memberjunction/ng-ai-test-harness": "5.
|
|
47
|
-
"@memberjunction/ng-artifacts": "5.
|
|
48
|
-
"@memberjunction/ng-auth-services": "5.
|
|
49
|
-
"@memberjunction/ng-base-application": "5.
|
|
50
|
-
"@memberjunction/ng-base-forms": "5.
|
|
51
|
-
"@memberjunction/ng-base-types": "5.
|
|
52
|
-
"@memberjunction/ng-container-directives": "5.
|
|
53
|
-
"@memberjunction/ng-conversations": "5.
|
|
54
|
-
"@memberjunction/ng-dashboard-viewer": "5.
|
|
55
|
-
"@memberjunction/ng-dashboards": "5.
|
|
56
|
-
"@memberjunction/ng-entity-form-dialog": "5.
|
|
57
|
-
"@memberjunction/ng-entity-permissions": "5.
|
|
58
|
-
"@memberjunction/ng-entity-viewer": "5.
|
|
59
|
-
"@memberjunction/ng-explorer-settings": "5.
|
|
60
|
-
"@memberjunction/ng-export-service": "5.
|
|
61
|
-
"@memberjunction/ng-feedback": "5.
|
|
62
|
-
"@memberjunction/ng-file-storage": "5.
|
|
63
|
-
"@memberjunction/ng-generic-dialog": "5.
|
|
64
|
-
"@memberjunction/ng-list-detail-grid": "5.
|
|
65
|
-
"@memberjunction/ng-list-management": "5.
|
|
66
|
-
"@memberjunction/ng-notifications": "5.
|
|
67
|
-
"@memberjunction/ng-pagination": "5.
|
|
68
|
-
"@memberjunction/ng-query-viewer": "5.
|
|
69
|
-
"@memberjunction/ng-record-changes": "5.
|
|
70
|
-
"@memberjunction/ng-record-selector": "5.
|
|
71
|
-
"@memberjunction/ng-record-tags": "5.
|
|
72
|
-
"@memberjunction/ng-resource-permissions": "5.
|
|
73
|
-
"@memberjunction/ng-search": "5.
|
|
74
|
-
"@memberjunction/ng-shared": "5.
|
|
75
|
-
"@memberjunction/ng-shared-generic": "5.
|
|
76
|
-
"@memberjunction/ng-ui-components": "5.
|
|
77
|
-
"@memberjunction/ng-user-avatar": "5.
|
|
78
|
-
"@memberjunction/ng-word-cloud": "5.
|
|
79
|
-
"@memberjunction/templates-base-types": "5.
|
|
35
|
+
"@memberjunction/ai-core-plus": "5.39.0",
|
|
36
|
+
"@memberjunction/ai-engine-base": "5.39.0",
|
|
37
|
+
"@memberjunction/communication-types": "5.39.0",
|
|
38
|
+
"@memberjunction/core": "5.39.0",
|
|
39
|
+
"@memberjunction/core-entities": "5.39.0",
|
|
40
|
+
"@memberjunction/entity-communications-client": "5.39.0",
|
|
41
|
+
"@memberjunction/export-engine": "5.39.0",
|
|
42
|
+
"@memberjunction/global": "5.39.0",
|
|
43
|
+
"@memberjunction/graphql-dataprovider": "5.39.0",
|
|
44
|
+
"@memberjunction/interactive-component-types": "5.39.0",
|
|
45
|
+
"@memberjunction/lists-base": "5.39.0",
|
|
46
|
+
"@memberjunction/ng-ai-test-harness": "5.39.0",
|
|
47
|
+
"@memberjunction/ng-artifacts": "5.39.0",
|
|
48
|
+
"@memberjunction/ng-auth-services": "5.39.0",
|
|
49
|
+
"@memberjunction/ng-base-application": "5.39.0",
|
|
50
|
+
"@memberjunction/ng-base-forms": "5.39.0",
|
|
51
|
+
"@memberjunction/ng-base-types": "5.39.0",
|
|
52
|
+
"@memberjunction/ng-container-directives": "5.39.0",
|
|
53
|
+
"@memberjunction/ng-conversations": "5.39.0",
|
|
54
|
+
"@memberjunction/ng-dashboard-viewer": "5.39.0",
|
|
55
|
+
"@memberjunction/ng-dashboards": "5.39.0",
|
|
56
|
+
"@memberjunction/ng-entity-form-dialog": "5.39.0",
|
|
57
|
+
"@memberjunction/ng-entity-permissions": "5.39.0",
|
|
58
|
+
"@memberjunction/ng-entity-viewer": "5.39.0",
|
|
59
|
+
"@memberjunction/ng-explorer-settings": "5.39.0",
|
|
60
|
+
"@memberjunction/ng-export-service": "5.39.0",
|
|
61
|
+
"@memberjunction/ng-feedback": "5.39.0",
|
|
62
|
+
"@memberjunction/ng-file-storage": "5.39.0",
|
|
63
|
+
"@memberjunction/ng-generic-dialog": "5.39.0",
|
|
64
|
+
"@memberjunction/ng-list-detail-grid": "5.39.0",
|
|
65
|
+
"@memberjunction/ng-list-management": "5.39.0",
|
|
66
|
+
"@memberjunction/ng-notifications": "5.39.0",
|
|
67
|
+
"@memberjunction/ng-pagination": "5.39.0",
|
|
68
|
+
"@memberjunction/ng-query-viewer": "5.39.0",
|
|
69
|
+
"@memberjunction/ng-record-changes": "5.39.0",
|
|
70
|
+
"@memberjunction/ng-record-selector": "5.39.0",
|
|
71
|
+
"@memberjunction/ng-record-tags": "5.39.0",
|
|
72
|
+
"@memberjunction/ng-resource-permissions": "5.39.0",
|
|
73
|
+
"@memberjunction/ng-search": "5.39.0",
|
|
74
|
+
"@memberjunction/ng-shared": "5.39.0",
|
|
75
|
+
"@memberjunction/ng-shared-generic": "5.39.0",
|
|
76
|
+
"@memberjunction/ng-ui-components": "5.39.0",
|
|
77
|
+
"@memberjunction/ng-user-avatar": "5.39.0",
|
|
78
|
+
"@memberjunction/ng-word-cloud": "5.39.0",
|
|
79
|
+
"@memberjunction/templates-base-types": "5.39.0",
|
|
80
80
|
"golden-layout": "^2.6.0",
|
|
81
81
|
"rxjs": "^7.8.2",
|
|
82
82
|
"tslib": "^2.8.1"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"form-resolver.service.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/form-resolver.service.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for FormResolverService.
|
|
3
|
-
*
|
|
4
|
-
* The service builds a SQL filter + ORDER BY against `EntityFormOverride`,
|
|
5
|
-
* runs it via RunView, and falls back to ClassFactory when no override matches.
|
|
6
|
-
* These tests capture the RunView call args (to validate filter shape) and
|
|
7
|
-
* stub responses to verify each resolution branch.
|
|
8
|
-
*/
|
|
9
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
10
|
-
// ----- Module mocks -------------------------------------------------------
|
|
11
|
-
// Mock UserInfoEngine — the resolver's variant preferences now persist via
|
|
12
|
-
// the user-settings table (cross-device, cross-browser) instead of
|
|
13
|
-
// localStorage. In-memory map mirrors the engine's cache behavior.
|
|
14
|
-
const settingsBacking = {};
|
|
15
|
-
vi.mock('@memberjunction/core-entities', () => ({
|
|
16
|
-
UserInfoEngine: {
|
|
17
|
-
Instance: {
|
|
18
|
-
GetSetting: (key) => settingsBacking[key],
|
|
19
|
-
SetSettingDebounced: (key, value) => {
|
|
20
|
-
settingsBacking[key] = value;
|
|
21
|
-
},
|
|
22
|
-
DeleteSetting: async (key) => {
|
|
23
|
-
delete settingsBacking[key];
|
|
24
|
-
return true;
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
}));
|
|
29
|
-
vi.mock('@angular/core', () => ({
|
|
30
|
-
Injectable: () => (target) => target,
|
|
31
|
-
}));
|
|
32
|
-
// RunView mock — we capture every call's params so tests can assert on them.
|
|
33
|
-
const runViewCalls = [];
|
|
34
|
-
let runViewResponse = { Success: true, Results: [] };
|
|
35
|
-
vi.mock('@memberjunction/core', () => ({
|
|
36
|
-
LogError: vi.fn(),
|
|
37
|
-
RunView: class {
|
|
38
|
-
static FromMetadataProvider(_provider) {
|
|
39
|
-
return new (class {
|
|
40
|
-
async RunView(params, user) {
|
|
41
|
-
runViewCalls.push({ params, user });
|
|
42
|
-
return runViewResponse;
|
|
43
|
-
}
|
|
44
|
-
})();
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
}));
|
|
48
|
-
// ClassFactory mock — controls what the fallback branch returns.
|
|
49
|
-
let classFactoryReg = null;
|
|
50
|
-
vi.mock('@memberjunction/global', () => ({
|
|
51
|
-
MJGlobal: {
|
|
52
|
-
Instance: {
|
|
53
|
-
ClassFactory: {
|
|
54
|
-
GetRegistration: vi.fn(() => classFactoryReg),
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
// The post-refactor resolver uses UUIDsEqual to match a user-selected
|
|
59
|
-
// variant ID; stub it with a case-insensitive string compare.
|
|
60
|
-
UUIDsEqual: (a, b) => (a ?? '').toLowerCase() === (b ?? '').toLowerCase(),
|
|
61
|
-
}));
|
|
62
|
-
vi.mock('@memberjunction/ng-base-forms', () => ({
|
|
63
|
-
BaseFormComponent: class FakeBaseFormComponent {
|
|
64
|
-
},
|
|
65
|
-
}));
|
|
66
|
-
const entity = { ID: 'entity-1', Name: 'Customer' };
|
|
67
|
-
const user = (overrides) => ({ ID: 'user-1', UserRoles: [], ...overrides });
|
|
68
|
-
const provider = {};
|
|
69
|
-
const overrideRow = (overrides) => ({
|
|
70
|
-
ID: 'row-1', EntityID: 'entity-1', ComponentID: 'comp-1',
|
|
71
|
-
Scope: 'Global', UserID: null, RoleID: null,
|
|
72
|
-
Priority: 0, Status: 'Active', ...overrides,
|
|
73
|
-
});
|
|
74
|
-
let service;
|
|
75
|
-
beforeEach(async () => {
|
|
76
|
-
runViewCalls.length = 0;
|
|
77
|
-
runViewResponse = { Success: true, Results: [] };
|
|
78
|
-
classFactoryReg = null;
|
|
79
|
-
for (const k of Object.keys(settingsBacking))
|
|
80
|
-
delete settingsBacking[k];
|
|
81
|
-
vi.clearAllMocks();
|
|
82
|
-
const mod = await import('../services/form-resolver.service');
|
|
83
|
-
service = new mod.FormResolverService();
|
|
84
|
-
});
|
|
85
|
-
// ----- Tests --------------------------------------------------------------
|
|
86
|
-
describe('FormResolverService.ResolveFormForEntity', () => {
|
|
87
|
-
it('returns kind=interactive when an override matches', async () => {
|
|
88
|
-
runViewResponse = { Success: true, Results: [overrideRow({})] };
|
|
89
|
-
const result = await service.ResolveFormForEntity(entity, user({}), provider);
|
|
90
|
-
expect(result.kind).toBe('interactive');
|
|
91
|
-
if (result.kind === 'interactive') {
|
|
92
|
-
expect(result.override.ComponentID).toBe('comp-1');
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
it('passes only "MJ: Entity Form Overrides" to RunView', async () => {
|
|
96
|
-
await service.ResolveFormForEntity(entity, user({}), provider);
|
|
97
|
-
expect(runViewCalls[0].params.EntityName).toBe('MJ: Entity Form Overrides');
|
|
98
|
-
});
|
|
99
|
-
it('filters by the entity ID and does NOT filter by Status (returns full variant list)', async () => {
|
|
100
|
-
// Post-refactor: the resolver lists ALL applicable variants (any
|
|
101
|
-
// Status) so the variant switcher can show Pending / Inactive
|
|
102
|
-
// alongside Active. The Active pick happens in-memory via pickActive.
|
|
103
|
-
await service.ResolveFormForEntity(entity, user({}), provider);
|
|
104
|
-
const filter = runViewCalls[0].params.ExtraFilter;
|
|
105
|
-
expect(filter).toContain(`EntityID='entity-1'`);
|
|
106
|
-
expect(filter).not.toContain(`Status='Active'`);
|
|
107
|
-
});
|
|
108
|
-
it('orders by User > Role > Global scope, then Priority DESC, then CreatedAt DESC', async () => {
|
|
109
|
-
await service.ResolveFormForEntity(entity, user({}), provider);
|
|
110
|
-
const orderBy = runViewCalls[0].params.OrderBy;
|
|
111
|
-
expect(orderBy).toContain(`WHEN 'User' THEN 1`);
|
|
112
|
-
expect(orderBy).toContain(`WHEN 'Role' THEN 2`);
|
|
113
|
-
expect(orderBy).toContain('Priority DESC');
|
|
114
|
-
expect(orderBy).toContain('__mj_CreatedAt DESC');
|
|
115
|
-
});
|
|
116
|
-
it('includes UserID match in the filter', async () => {
|
|
117
|
-
await service.ResolveFormForEntity(entity, user({ ID: 'me' }), provider);
|
|
118
|
-
const filter = runViewCalls[0].params.ExtraFilter;
|
|
119
|
-
expect(filter).toContain(`Scope='User' AND UserID='me'`);
|
|
120
|
-
});
|
|
121
|
-
it('includes role-membership clause for each role the user has', async () => {
|
|
122
|
-
const u = user({ UserRoles: [{ RoleID: 'r1' }, { RoleID: 'r2' }] });
|
|
123
|
-
await service.ResolveFormForEntity(entity, u, provider);
|
|
124
|
-
const filter = runViewCalls[0].params.ExtraFilter;
|
|
125
|
-
expect(filter).toContain(`Scope='Role' AND RoleID IN ('r1','r2')`);
|
|
126
|
-
});
|
|
127
|
-
it('emits an always-false role clause when the user has no roles', async () => {
|
|
128
|
-
await service.ResolveFormForEntity(entity, user({ UserRoles: [] }), provider);
|
|
129
|
-
const filter = runViewCalls[0].params.ExtraFilter;
|
|
130
|
-
// No roles = the user can never match a Role-scoped row. We encode that
|
|
131
|
-
// as (1=0) rather than producing `IN ()` which is invalid SQL.
|
|
132
|
-
expect(filter).toContain('(1=0)');
|
|
133
|
-
expect(filter).not.toContain('IN ()');
|
|
134
|
-
});
|
|
135
|
-
it('does NOT cap the query at MaxRows=1 — needs the full variant list', async () => {
|
|
136
|
-
// Post-refactor: the resolver returns variants for the picker, so it
|
|
137
|
-
// can't limit to one row. The pickActive() in-memory still selects
|
|
138
|
-
// the top match for the default-rendered form.
|
|
139
|
-
await service.ResolveFormForEntity(entity, user({}), provider);
|
|
140
|
-
expect(runViewCalls[0].params.MaxRows).toBeUndefined();
|
|
141
|
-
});
|
|
142
|
-
it('returns variants list alongside the resolved override', async () => {
|
|
143
|
-
const variants = [
|
|
144
|
-
overrideRow({ ID: 'v-active', Status: 'Active', Scope: 'User', UserID: 'user-1' }),
|
|
145
|
-
overrideRow({ ID: 'v-pending', Status: 'Pending', Scope: 'User', UserID: 'user-1' }),
|
|
146
|
-
overrideRow({ ID: 'v-inactive', Status: 'Inactive', Scope: 'Global' }),
|
|
147
|
-
];
|
|
148
|
-
runViewResponse = { Success: true, Results: variants };
|
|
149
|
-
const result = await service.ResolveFormForEntity(entity, user({}), provider);
|
|
150
|
-
expect(result.variants?.length).toBe(3);
|
|
151
|
-
expect(result.variants?.map(v => v.ID).sort()).toEqual(['v-active', 'v-inactive', 'v-pending']);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
describe('FormResolverService.ListVariantsForEntity', () => {
|
|
155
|
-
it('returns the full applicable list (any status)', async () => {
|
|
156
|
-
runViewResponse = {
|
|
157
|
-
Success: true,
|
|
158
|
-
Results: [
|
|
159
|
-
overrideRow({ ID: 'v1', Status: 'Active' }),
|
|
160
|
-
overrideRow({ ID: 'v2', Status: 'Pending' }),
|
|
161
|
-
],
|
|
162
|
-
};
|
|
163
|
-
const variants = await service.ListVariantsForEntity(entity, user({}), provider);
|
|
164
|
-
expect(variants.length).toBe(2);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
describe('FormResolverService session-local variant selection', () => {
|
|
168
|
-
beforeEach(() => {
|
|
169
|
-
// The resolver writes to localStorage; reset before each test.
|
|
170
|
-
if (typeof window !== 'undefined' && window.localStorage) {
|
|
171
|
-
window.localStorage.clear();
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
it('honours a saved variant ID over the default Active pick', async () => {
|
|
175
|
-
runViewResponse = {
|
|
176
|
-
Success: true,
|
|
177
|
-
Results: [
|
|
178
|
-
overrideRow({ ID: 'v-default-active', Status: 'Active', Scope: 'Global', Priority: 0 }),
|
|
179
|
-
overrideRow({ ID: 'v-user-selected', Status: 'Active', Scope: 'User', UserID: 'user-1', Priority: 0 }),
|
|
180
|
-
],
|
|
181
|
-
};
|
|
182
|
-
// Save the user's choice
|
|
183
|
-
service.SetSelectedVariant(entity.Name, 'v-user-selected');
|
|
184
|
-
const result = await service.ResolveFormForEntity(entity, user({}), provider);
|
|
185
|
-
expect(result.kind).toBe('interactive');
|
|
186
|
-
if (result.kind === 'interactive') {
|
|
187
|
-
expect(result.override.ID).toBe('v-user-selected');
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
it('clears a saved variant ID that no longer matches an Active row', async () => {
|
|
191
|
-
runViewResponse = {
|
|
192
|
-
Success: true,
|
|
193
|
-
Results: [overrideRow({ ID: 'v-active', Status: 'Active' })],
|
|
194
|
-
};
|
|
195
|
-
service.SetSelectedVariant(entity.Name, 'v-stale');
|
|
196
|
-
const result = await service.ResolveFormForEntity(entity, user({}), provider);
|
|
197
|
-
expect(result.kind).toBe('interactive');
|
|
198
|
-
if (result.kind === 'interactive') {
|
|
199
|
-
expect(result.override.ID).toBe('v-active');
|
|
200
|
-
}
|
|
201
|
-
// Stale selection should have been cleared
|
|
202
|
-
expect(service.GetSelectedVariant(entity.Name)).toBeNull();
|
|
203
|
-
});
|
|
204
|
-
it('ClearSelectedVariant wipes the stored choice', () => {
|
|
205
|
-
service.SetSelectedVariant(entity.Name, 'v1');
|
|
206
|
-
expect(service.GetSelectedVariant(entity.Name)).toBe('v1');
|
|
207
|
-
service.ClearSelectedVariant(entity.Name);
|
|
208
|
-
expect(service.GetSelectedVariant(entity.Name)).toBeNull();
|
|
209
|
-
});
|
|
210
|
-
it('SetExplicitDefault stores the sentinel so pickActive returns null even when Active overrides exist', async () => {
|
|
211
|
-
// Hardcoded — matches FormResolverService.EXPLICIT_DEFAULT_SENTINEL.
|
|
212
|
-
const SENTINEL = '__codegen-default__';
|
|
213
|
-
service.SetExplicitDefault(entity.Name);
|
|
214
|
-
expect(service.GetSelectedVariant(entity.Name)).toBe(SENTINEL);
|
|
215
|
-
runViewResponse = { Success: true, Results: [overrideRow({ ID: 'v-active', Scope: 'Global', Status: 'Active' })] };
|
|
216
|
-
const result = await service.ResolveFormForEntity(entity, user({}), provider);
|
|
217
|
-
// Even with an Active override in the variants list, the sentinel
|
|
218
|
-
// forces a fall-through to the CodeGen / Angular fallback.
|
|
219
|
-
expect(result.kind).not.toBe('interactive');
|
|
220
|
-
});
|
|
221
|
-
it('is case-insensitive on the entity name', () => {
|
|
222
|
-
service.SetSelectedVariant('Customer', 'v1');
|
|
223
|
-
expect(service.GetSelectedVariant('customer')).toBe('v1');
|
|
224
|
-
expect(service.GetSelectedVariant('CUSTOMER')).toBe('v1');
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
describe('FormResolverService.ResolveFormForEntity — fallback behaviour', () => {
|
|
228
|
-
it('uses ResultType=simple to skip BaseEntity hydration on the hot path', async () => {
|
|
229
|
-
await service.ResolveFormForEntity(entity, user({}), provider);
|
|
230
|
-
expect(runViewCalls[0].params.ResultType).toBe('simple');
|
|
231
|
-
});
|
|
232
|
-
it('falls back to ClassFactory when no override matches', async () => {
|
|
233
|
-
runViewResponse = { Success: true, Results: [] };
|
|
234
|
-
classFactoryReg = { SubClass: class FakeForm {
|
|
235
|
-
} };
|
|
236
|
-
const result = await service.ResolveFormForEntity(entity, user({}), provider);
|
|
237
|
-
expect(result.kind).toBe('class');
|
|
238
|
-
if (result.kind === 'class') {
|
|
239
|
-
expect(result.subClass).toBe(classFactoryReg.SubClass);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
it('returns kind=none when neither override nor ClassFactory has a form', async () => {
|
|
243
|
-
runViewResponse = { Success: true, Results: [] };
|
|
244
|
-
classFactoryReg = null;
|
|
245
|
-
const result = await service.ResolveFormForEntity(entity, user({}), provider);
|
|
246
|
-
expect(result.kind).toBe('none');
|
|
247
|
-
});
|
|
248
|
-
it('falls through when the RunView fails (treats lookup failure as "no override")', async () => {
|
|
249
|
-
runViewResponse = { Success: false, ErrorMessage: 'db down' };
|
|
250
|
-
classFactoryReg = { SubClass: class FakeForm {
|
|
251
|
-
} };
|
|
252
|
-
const result = await service.ResolveFormForEntity(entity, user({}), provider);
|
|
253
|
-
// Lookup failure should NOT block the user from seeing the class-based form
|
|
254
|
-
// — better to show *something* than to fail closed on every entity load.
|
|
255
|
-
expect(result.kind).toBe('class');
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
//# sourceMappingURL=form-resolver.service.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"form-resolver.service.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/form-resolver.service.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE9D,6EAA6E;AAE7E,2EAA2E;AAC3E,mEAAmE;AACnE,mEAAmE;AACnE,MAAM,eAAe,GAA2B,EAAE,CAAC;AACnD,EAAE,CAAC,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5C,cAAc,EAAE;QACZ,QAAQ,EAAE;YACN,UAAU,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;YACjD,mBAAmB,EAAE,CAAC,GAAW,EAAE,KAAa,EAAE,EAAE;gBAChD,eAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACjC,CAAC;YACD,aAAa,EAAE,KAAK,EAAE,GAAW,EAAE,EAAE;gBACjC,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;gBAC5B,OAAO,IAAI,CAAC;YAChB,CAAC;SACJ;KACJ;CACJ,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5B,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM;CACjD,CAAC,CAAC,CAAC;AAEJ,6EAA6E;AAC7E,MAAM,YAAY,GAAsC,EAAE,CAAC;AAC3D,IAAI,eAAe,GAAiE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAEnH,EAAE,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAC;IACnC,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE;IACjB,OAAO,EAAE;QACL,MAAM,CAAC,oBAAoB,CAAC,SAAc;YACtC,OAAO,IAAI,CAAC;gBACR,KAAK,CAAC,OAAO,CAAC,MAAW,EAAE,IAAS;oBAChC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;oBACpC,OAAO,eAAe,CAAC;gBAC3B,CAAC;aACJ,CAAC,EAAE,CAAC;QACT,CAAC;KACJ;CACJ,CAAC,CAAC,CAAC;AAEJ,iEAAiE;AACjE,IAAI,eAAe,GAA6B,IAAI,CAAC;AACrD,EAAE,CAAC,IAAI,CAAC,wBAAwB,EAAE,GAAG,EAAE,CAAC,CAAC;IACrC,QAAQ,EAAE;QACN,QAAQ,EAAE;YACN,YAAY,EAAE;gBACV,eAAe,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC;aAChD;SACJ;KACJ;IACD,sEAAsE;IACtE,8DAA8D;IAC9D,UAAU,EAAE,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE;CAC5F,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5C,iBAAiB,EAAE,MAAM,qBAAqB;KAAG;CACpD,CAAC,CAAC,CAAC;AAMJ,MAAM,MAAM,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAS,CAAC;AAC3D,MAAM,IAAI,GAAG,CAAC,SAAwE,EAAE,EAAE,CACtF,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,SAAS,EAAU,CAAA,CAAC;AAC3D,MAAM,QAAQ,GAAG,EAAS,CAAC;AAE3B,MAAM,WAAW,GAAG,CAAC,SAKnB,EAAE,EAAE,CAAC,CAAC;IACJ,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ;IACxD,KAAK,EAAE,QAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI;IACpD,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,QAAiB,EAAE,GAAG,SAAS;CACvD,CAAC,CAAC;AAEH,IAAI,OAA4B,CAAC;AAEjC,UAAU,CAAC,KAAK,IAAI,EAAE;IAClB,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IACxB,eAAe,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACjD,eAAe,GAAG,IAAI,CAAC;IACvB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC;QAAE,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC;IACxE,EAAE,CAAC,aAAa,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,mCAAmC,CAAC,CAAC;IAC9D,OAAO,GAAG,IAAI,GAAG,CAAC,mBAAmB,EAAE,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEH,6EAA6E;AAE7E,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IAEtD,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QAC/D,eAAe,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAEhE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE9E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvD,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC/D,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oFAAoF,EAAE,KAAK,IAAI,EAAE;QAChG,iEAAiE;QACjE,8DAA8D;QAC9D,sEAAsE;QACtE,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;QAC3F,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC/D,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QAC/C,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAChD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAChD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;QAC3C,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACpE,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,wCAAwC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9E,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC;QAClD,wEAAwE;QACxE,+DAA+D;QAC/D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QAC/E,qEAAqE;QACrE,mEAAmE;QACnE,+CAA+C;QAC/C,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC/D,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,QAAQ,GAAG;YACb,WAAW,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;YAClF,WAAW,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;YACpF,WAAW,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;SACzE,CAAC;QACF,eAAe,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;QACvD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9E,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2CAA2C,EAAE,GAAG,EAAE;IAEvD,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC3D,eAAe,GAAG;YACd,OAAO,EAAE,IAAI;YACb,OAAO,EAAE;gBACL,WAAW,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBAC3C,WAAW,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;aAC/C;SACJ,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QACjF,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAEjE,UAAU,CAAC,GAAG,EAAE;QACZ,+DAA+D;QAC/D,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACvD,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAChC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACrE,eAAe,GAAG;YACd,OAAO,EAAE,IAAI;YACb,OAAO,EAAE;gBACL,WAAW,CAAC,EAAE,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;gBACvF,WAAW,CAAC,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;aACzG;SACJ,CAAC;QACF,yBAAyB;QACzB,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACvD,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC5E,eAAe,GAAG;YACd,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,CAAE,WAAW,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAE;SACjE,CAAC;QACF,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChD,CAAC;QACD,2CAA2C;QAC3C,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACpD,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC9C,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oGAAoG,EAAE,KAAK,IAAI,EAAE;QAChH,qEAAqE;QACrE,MAAM,QAAQ,GAAG,qBAAqB,CAAC;QACvC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/D,eAAe,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC;QACnH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9E,kEAAkE;QAClE,2DAA2D;QAC3D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAC9C,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC7C,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+DAA+D,EAAE,GAAG,EAAE;IAE3E,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC/D,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACjE,eAAe,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACjD,eAAe,GAAG,EAAE,QAAQ,EAAE,MAAM,QAAQ;aAAG,EAAE,CAAC;QAElD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE9E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC3D,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACjF,eAAe,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACjD,eAAe,GAAG,IAAI,CAAC;QAEvB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE9E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;QAC3F,eAAe,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;QAC9D,eAAe,GAAG,EAAE,QAAQ,EAAE,MAAM,QAAQ;aAAG,EAAE,CAAC;QAElD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE9E,4EAA4E;QAC5E,yEAAyE;QACzE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Tests for FormResolverService.\n *\n * The service builds a SQL filter + ORDER BY against `EntityFormOverride`,\n * runs it via RunView, and falls back to ClassFactory when no override matches.\n * These tests capture the RunView call args (to validate filter shape) and\n * stub responses to verify each resolution branch.\n */\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\n\n// ----- Module mocks -------------------------------------------------------\n\n// Mock UserInfoEngine — the resolver's variant preferences now persist via\n// the user-settings table (cross-device, cross-browser) instead of\n// localStorage. In-memory map mirrors the engine's cache behavior.\nconst settingsBacking: Record<string, string> = {};\nvi.mock('@memberjunction/core-entities', () => ({\n UserInfoEngine: {\n Instance: {\n GetSetting: (key: string) => settingsBacking[key],\n SetSettingDebounced: (key: string, value: string) => {\n settingsBacking[key] = value;\n },\n DeleteSetting: async (key: string) => {\n delete settingsBacking[key];\n return true;\n },\n },\n },\n}));\n\nvi.mock('@angular/core', () => ({\n Injectable: () => (target: Function) => target,\n}));\n\n// RunView mock — we capture every call's params so tests can assert on them.\nconst runViewCalls: Array<{ params: any; user: any }> = [];\nlet runViewResponse: { Success: boolean; Results?: any[]; ErrorMessage?: string } = { Success: true, Results: [] };\n\nvi.mock('@memberjunction/core', () => ({\n LogError: vi.fn(),\n RunView: class {\n static FromMetadataProvider(_provider: any) {\n return new (class {\n async RunView(params: any, user: any) {\n runViewCalls.push({ params, user });\n return runViewResponse;\n }\n })();\n }\n },\n}));\n\n// ClassFactory mock — controls what the fallback branch returns.\nlet classFactoryReg: { SubClass: any } | null = null;\nvi.mock('@memberjunction/global', () => ({\n MJGlobal: {\n Instance: {\n ClassFactory: {\n GetRegistration: vi.fn(() => classFactoryReg),\n },\n },\n },\n // The post-refactor resolver uses UUIDsEqual to match a user-selected\n // variant ID; stub it with a case-insensitive string compare.\n UUIDsEqual: (a: string, b: string) => (a ?? '').toLowerCase() === (b ?? '').toLowerCase(),\n}));\n\nvi.mock('@memberjunction/ng-base-forms', () => ({\n BaseFormComponent: class FakeBaseFormComponent {},\n}));\n\n// ----- Test setup ---------------------------------------------------------\n\nimport type { FormResolverService } from '../services/form-resolver.service';\n\nconst entity = { ID: 'entity-1', Name: 'Customer' } as any;\nconst user = (overrides: Partial<{ ID: string; UserRoles: Array<{ RoleID: string }> }>) =>\n ({ ID: 'user-1', UserRoles: [], ...overrides } as any);\nconst provider = {} as any;\n\nconst overrideRow = (overrides: Partial<{\n ID: string; EntityID: string; ComponentID: string;\n Scope: 'User' | 'Role' | 'Global';\n UserID: string | null; RoleID: string | null;\n Priority: number; Status: 'Active' | 'Inactive' | 'Pending';\n}>) => ({\n ID: 'row-1', EntityID: 'entity-1', ComponentID: 'comp-1',\n Scope: 'Global' as const, UserID: null, RoleID: null,\n Priority: 0, Status: 'Active' as const, ...overrides,\n});\n\nlet service: FormResolverService;\n\nbeforeEach(async () => {\n runViewCalls.length = 0;\n runViewResponse = { Success: true, Results: [] };\n classFactoryReg = null;\n for (const k of Object.keys(settingsBacking)) delete settingsBacking[k];\n vi.clearAllMocks();\n const mod = await import('../services/form-resolver.service');\n service = new mod.FormResolverService();\n});\n\n// ----- Tests --------------------------------------------------------------\n\ndescribe('FormResolverService.ResolveFormForEntity', () => {\n\n it('returns kind=interactive when an override matches', async () => {\n runViewResponse = { Success: true, Results: [overrideRow({})] };\n\n const result = await service.ResolveFormForEntity(entity, user({}), provider);\n\n expect(result.kind).toBe('interactive');\n if (result.kind === 'interactive') {\n expect(result.override.ComponentID).toBe('comp-1');\n }\n });\n\n it('passes only \"MJ: Entity Form Overrides\" to RunView', async () => {\n await service.ResolveFormForEntity(entity, user({}), provider);\n expect(runViewCalls[0].params.EntityName).toBe('MJ: Entity Form Overrides');\n });\n\n it('filters by the entity ID and does NOT filter by Status (returns full variant list)', async () => {\n // Post-refactor: the resolver lists ALL applicable variants (any\n // Status) so the variant switcher can show Pending / Inactive\n // alongside Active. The Active pick happens in-memory via pickActive.\n await service.ResolveFormForEntity(entity, user({}), provider);\n const filter = runViewCalls[0].params.ExtraFilter;\n expect(filter).toContain(`EntityID='entity-1'`);\n expect(filter).not.toContain(`Status='Active'`);\n });\n\n it('orders by User > Role > Global scope, then Priority DESC, then CreatedAt DESC', async () => {\n await service.ResolveFormForEntity(entity, user({}), provider);\n const orderBy = runViewCalls[0].params.OrderBy;\n expect(orderBy).toContain(`WHEN 'User' THEN 1`);\n expect(orderBy).toContain(`WHEN 'Role' THEN 2`);\n expect(orderBy).toContain('Priority DESC');\n expect(orderBy).toContain('__mj_CreatedAt DESC');\n });\n\n it('includes UserID match in the filter', async () => {\n await service.ResolveFormForEntity(entity, user({ ID: 'me' }), provider);\n const filter = runViewCalls[0].params.ExtraFilter;\n expect(filter).toContain(`Scope='User' AND UserID='me'`);\n });\n\n it('includes role-membership clause for each role the user has', async () => {\n const u = user({ UserRoles: [{ RoleID: 'r1' }, { RoleID: 'r2' }] });\n await service.ResolveFormForEntity(entity, u, provider);\n const filter = runViewCalls[0].params.ExtraFilter;\n expect(filter).toContain(`Scope='Role' AND RoleID IN ('r1','r2')`);\n });\n\n it('emits an always-false role clause when the user has no roles', async () => {\n await service.ResolveFormForEntity(entity, user({ UserRoles: [] }), provider);\n const filter = runViewCalls[0].params.ExtraFilter;\n // No roles = the user can never match a Role-scoped row. We encode that\n // as (1=0) rather than producing `IN ()` which is invalid SQL.\n expect(filter).toContain('(1=0)');\n expect(filter).not.toContain('IN ()');\n });\n\n it('does NOT cap the query at MaxRows=1 — needs the full variant list', async () => {\n // Post-refactor: the resolver returns variants for the picker, so it\n // can't limit to one row. The pickActive() in-memory still selects\n // the top match for the default-rendered form.\n await service.ResolveFormForEntity(entity, user({}), provider);\n expect(runViewCalls[0].params.MaxRows).toBeUndefined();\n });\n\n it('returns variants list alongside the resolved override', async () => {\n const variants = [\n overrideRow({ ID: 'v-active', Status: 'Active', Scope: 'User', UserID: 'user-1' }),\n overrideRow({ ID: 'v-pending', Status: 'Pending', Scope: 'User', UserID: 'user-1' }),\n overrideRow({ ID: 'v-inactive', Status: 'Inactive', Scope: 'Global' }),\n ];\n runViewResponse = { Success: true, Results: variants };\n const result = await service.ResolveFormForEntity(entity, user({}), provider);\n expect(result.variants?.length).toBe(3);\n expect(result.variants?.map(v => v.ID).sort()).toEqual(['v-active', 'v-inactive', 'v-pending']);\n });\n});\n\ndescribe('FormResolverService.ListVariantsForEntity', () => {\n\n it('returns the full applicable list (any status)', async () => {\n runViewResponse = {\n Success: true,\n Results: [\n overrideRow({ ID: 'v1', Status: 'Active' }),\n overrideRow({ ID: 'v2', Status: 'Pending' }),\n ],\n };\n const variants = await service.ListVariantsForEntity(entity, user({}), provider);\n expect(variants.length).toBe(2);\n });\n});\n\ndescribe('FormResolverService session-local variant selection', () => {\n\n beforeEach(() => {\n // The resolver writes to localStorage; reset before each test.\n if (typeof window !== 'undefined' && window.localStorage) {\n window.localStorage.clear();\n }\n });\n\n it('honours a saved variant ID over the default Active pick', async () => {\n runViewResponse = {\n Success: true,\n Results: [\n overrideRow({ ID: 'v-default-active', Status: 'Active', Scope: 'Global', Priority: 0 }),\n overrideRow({ ID: 'v-user-selected', Status: 'Active', Scope: 'User', UserID: 'user-1', Priority: 0 }),\n ],\n };\n // Save the user's choice\n service.SetSelectedVariant(entity.Name, 'v-user-selected');\n const result = await service.ResolveFormForEntity(entity, user({}), provider);\n expect(result.kind).toBe('interactive');\n if (result.kind === 'interactive') {\n expect(result.override.ID).toBe('v-user-selected');\n }\n });\n\n it('clears a saved variant ID that no longer matches an Active row', async () => {\n runViewResponse = {\n Success: true,\n Results: [ overrideRow({ ID: 'v-active', Status: 'Active' }) ],\n };\n service.SetSelectedVariant(entity.Name, 'v-stale');\n const result = await service.ResolveFormForEntity(entity, user({}), provider);\n expect(result.kind).toBe('interactive');\n if (result.kind === 'interactive') {\n expect(result.override.ID).toBe('v-active');\n }\n // Stale selection should have been cleared\n expect(service.GetSelectedVariant(entity.Name)).toBeNull();\n });\n\n it('ClearSelectedVariant wipes the stored choice', () => {\n service.SetSelectedVariant(entity.Name, 'v1');\n expect(service.GetSelectedVariant(entity.Name)).toBe('v1');\n service.ClearSelectedVariant(entity.Name);\n expect(service.GetSelectedVariant(entity.Name)).toBeNull();\n });\n\n it('SetExplicitDefault stores the sentinel so pickActive returns null even when Active overrides exist', async () => {\n // Hardcoded — matches FormResolverService.EXPLICIT_DEFAULT_SENTINEL.\n const SENTINEL = '__codegen-default__';\n service.SetExplicitDefault(entity.Name);\n expect(service.GetSelectedVariant(entity.Name)).toBe(SENTINEL);\n runViewResponse = { Success: true, Results: [overrideRow({ ID: 'v-active', Scope: 'Global', Status: 'Active' })] };\n const result = await service.ResolveFormForEntity(entity, user({}), provider);\n // Even with an Active override in the variants list, the sentinel\n // forces a fall-through to the CodeGen / Angular fallback.\n expect(result.kind).not.toBe('interactive');\n });\n\n it('is case-insensitive on the entity name', () => {\n service.SetSelectedVariant('Customer', 'v1');\n expect(service.GetSelectedVariant('customer')).toBe('v1');\n expect(service.GetSelectedVariant('CUSTOMER')).toBe('v1');\n });\n});\n\ndescribe('FormResolverService.ResolveFormForEntity — fallback behaviour', () => {\n\n it('uses ResultType=simple to skip BaseEntity hydration on the hot path', async () => {\n await service.ResolveFormForEntity(entity, user({}), provider);\n expect(runViewCalls[0].params.ResultType).toBe('simple');\n });\n\n it('falls back to ClassFactory when no override matches', async () => {\n runViewResponse = { Success: true, Results: [] };\n classFactoryReg = { SubClass: class FakeForm {} };\n\n const result = await service.ResolveFormForEntity(entity, user({}), provider);\n\n expect(result.kind).toBe('class');\n if (result.kind === 'class') {\n expect(result.subClass).toBe(classFactoryReg.SubClass);\n }\n });\n\n it('returns kind=none when neither override nor ClassFactory has a form', async () => {\n runViewResponse = { Success: true, Results: [] };\n classFactoryReg = null;\n\n const result = await service.ResolveFormForEntity(entity, user({}), provider);\n\n expect(result.kind).toBe('none');\n });\n\n it('falls through when the RunView fails (treats lookup failure as \"no override\")', async () => {\n runViewResponse = { Success: false, ErrorMessage: 'db down' };\n classFactoryReg = { SubClass: class FakeForm {} };\n\n const result = await service.ResolveFormForEntity(entity, user({}), provider);\n\n // Lookup failure should NOT block the user from seeing the class-based form\n // — better to show *something* than to fail closed on every entity load.\n expect(result.kind).toBe('class');\n });\n});\n"]}
|