diff --git a/.changeset/four-drinks-sneeze.md b/.changeset/four-drinks-sneeze.md new file mode 100644 index 000000000..cb4009eba --- /dev/null +++ b/.changeset/four-drinks-sneeze.md @@ -0,0 +1,5 @@ +--- +"@itwin/presentation-shared": major +--- + +Made `EC.RelationshipConstraint.multiplicity` required. diff --git a/.changeset/ninety-mails-doubt.md b/.changeset/ninety-mails-doubt.md new file mode 100644 index 000000000..edb877ec4 --- /dev/null +++ b/.changeset/ninety-mails-doubt.md @@ -0,0 +1,8 @@ +--- +"@itwin/presentation-hierarchies-react": patch +"@itwin/presentation-core-interop": patch +"@itwin/presentation-components": patch +"@itwin/presentation-testing": patch +--- + +Removed unnecessary, always-truthy condition checks. diff --git a/apps/full-stack-tests/src/ECDbUtils.ts b/apps/full-stack-tests/src/ECDbUtils.ts index aec670516..fe605b5af 100644 --- a/apps/full-stack-tests/src/ECDbUtils.ts +++ b/apps/full-stack-tests/src/ECDbUtils.ts @@ -175,7 +175,7 @@ export class ECDbBuilder { } function isBinding(value: ECSqlBinding | PrimitiveValue): value is ECSqlBinding { - return typeof value === "object" && (value as ECSqlBinding).type !== undefined && (value as ECSqlBinding).value !== undefined; + return typeof value === "object" && "type" in value && "value" in value; } export async function createECDb( diff --git a/apps/full-stack-tests/src/IModelUtils.ts b/apps/full-stack-tests/src/IModelUtils.ts index b3515fb8a..2a6db87d8 100644 --- a/apps/full-stack-tests/src/IModelUtils.ts +++ b/apps/full-stack-tests/src/IModelUtils.ts @@ -47,9 +47,6 @@ async function cloneIModel( IModelJsFs.copySync(sourceIModelPath, targetIModelPath); const imodel = StandaloneDb.openFile(targetIModelPath, OpenMode.ReadWrite); - if (!imodel) { - throw new Error("Failed to open cloned iModel"); - } try { const res = await setup(new TestIModelBuilderImpl(imodel)); imodel.saveChanges("Updated cloned iModel"); @@ -93,7 +90,7 @@ export function createSchemaContext(imodel: IModelConnection | IModelDb | ECDb) async getSchemaInfo(schemaKey: Readonly, matchType: SchemaMatchType, schemaContext: SchemaContext): Promise { const schemaJson = imodel.getSchemaProps(schemaKey.name); const schemaInfo = await Schema.startLoadingFromJson(schemaJson, schemaContext); - if (schemaInfo !== undefined && schemaInfo.schemaKey.matches(schemaKey as SchemaKey, matchType)) { + if (schemaInfo.schemaKey.matches(schemaKey as SchemaKey, matchType)) { return schemaInfo; } return undefined; diff --git a/apps/full-stack-tests/src/components/ErrorBoundary.tsx b/apps/full-stack-tests/src/components/ErrorBoundary.tsx index 1e74d6088..7bcbc43a7 100644 --- a/apps/full-stack-tests/src/components/ErrorBoundary.tsx +++ b/apps/full-stack-tests/src/components/ErrorBoundary.tsx @@ -26,7 +26,7 @@ export class ErrorBoundary extends Component<{ children: React.ReactNode }, { er public override render() { // in case we got an error - render the error message if (this.state.error) { - return this.state.error?.message ?? "Error"; + return this.state.error.message; } // otherwise - render provided child component diff --git a/apps/full-stack-tests/src/components/properties/PropertyPaneDataProvider.test.ts b/apps/full-stack-tests/src/components/properties/PropertyPaneDataProvider.test.ts index 6122822d6..377c620c5 100644 --- a/apps/full-stack-tests/src/components/properties/PropertyPaneDataProvider.test.ts +++ b/apps/full-stack-tests/src/components/properties/PropertyPaneDataProvider.test.ts @@ -482,7 +482,7 @@ describe("PropertyDataProvider", async () => { const structArrayRecord = properties.records["/selected-item/"].find((r) => r.property.name.endsWith("StructArrayProperty")); assert(structArrayRecord?.value.valueFormat === PropertyValueFormat.Array); const structArrayItemRecord = structArrayRecord.value.items[0]; - assert(structArrayItemRecord?.value.valueFormat === PropertyValueFormat.Struct); + assert(structArrayItemRecord.value.valueFormat === PropertyValueFormat.Struct); const structArrayItemMemberRecord = structArrayItemRecord.value.members.StringMember; const structArrayMemberField = (await provider.getFieldByPropertyDescription(structArrayItemMemberRecord.property)) as PropertiesField; expect(structArrayMemberField).to.containSubset({ diff --git a/apps/full-stack-tests/src/unified-selection/SelectionSync.test.ts b/apps/full-stack-tests/src/unified-selection/SelectionSync.test.ts index df9e35881..c742f12b2 100644 --- a/apps/full-stack-tests/src/unified-selection/SelectionSync.test.ts +++ b/apps/full-stack-tests/src/unified-selection/SelectionSync.test.ts @@ -66,6 +66,7 @@ describe("Unified selection sync with iModel", () => { }); afterEach(async () => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (imodel) { selectionStorage.clearStorage({ imodelKey: createIModelKey(imodel) }); await imodel.close(); diff --git a/apps/load-tests/tests/src/processors/common.ts b/apps/load-tests/tests/src/processors/common.ts index b4711c649..1c6dcb01e 100644 --- a/apps/load-tests/tests/src/processors/common.ts +++ b/apps/load-tests/tests/src/processors/common.ts @@ -3,6 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ import { VUContext, VUEvents } from "artillery"; import { decompress as brotliDecompress } from "brotli"; diff --git a/apps/load-tests/tests/src/processors/models-tree-stateless.ts b/apps/load-tests/tests/src/processors/models-tree-stateless.ts index abf6ee19e..33a9c9a69 100644 --- a/apps/load-tests/tests/src/processors/models-tree-stateless.ts +++ b/apps/load-tests/tests/src/processors/models-tree-stateless.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /* eslint-disable @itwin/no-internal */ /* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ import { VUContext, VUEvents } from "artillery"; import { StopWatch } from "@itwin/core-bentley"; diff --git a/apps/performance-tests/src/hierarchies/Search.test.ts b/apps/performance-tests/src/hierarchies/Search.test.ts index 2b041dfd9..cd8db8a68 100644 --- a/apps/performance-tests/src/hierarchies/Search.test.ts +++ b/apps/performance-tests/src/hierarchies/Search.test.ts @@ -88,7 +88,6 @@ describe("search", () => { return createHierarchyLevelDefinition(imodelAccess, (alias) => `WHERE ${alias}.ECInstanceId = ${physicalElementsSmallestDecimalId}`); } if ( - props.parentNode && HierarchyNode.isInstancesNode(props.parentNode) && props.parentNode.key.instanceKeys.some(({ id }) => Id64.getLocalId(id) === physicalElementsSmallestDecimalId) ) { @@ -96,7 +95,6 @@ describe("search", () => { } if ( - props.parentNode && HierarchyNode.isInstancesNode(props.parentNode) && props.parentNode.key.instanceKeys.some(({ id }) => parentIdsArr.includes(Id64.getLocalId(id))) ) { diff --git a/apps/performance-tests/src/util/MainThreadBlocksDetector.ts b/apps/performance-tests/src/util/MainThreadBlocksDetector.ts index 577773912..2788971bb 100644 --- a/apps/performance-tests/src/util/MainThreadBlocksDetector.ts +++ b/apps/performance-tests/src/util/MainThreadBlocksDetector.ts @@ -59,6 +59,7 @@ export class MainThreadBlocksDetector { if (lateAmount > threshold) { log(() => `${lateAmount} ms, ${lastTime.toISOString()} - ${currentTime.toISOString()}`); this._samples.insert(lateAmount); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (ENABLE_PINGS) { log(() => `[${currentTime.toISOString()}] Ping`); } diff --git a/apps/test-app/frontend/src/components/app/App.tsx b/apps/test-app/frontend/src/components/app/App.tsx index bef3948ab..c318fb75f 100644 --- a/apps/test-app/frontend/src/components/app/App.tsx +++ b/apps/test-app/frontend/src/components/app/App.tsx @@ -102,7 +102,7 @@ export function App() { return () => { IModelApp.resetFormatsProvider(); - removeFormatterListener?.(); + removeFormatterListener(); void IModelApp.quantityFormatter.resetToUseInternalUnitsProvider(); }; }, [state.imodel]); @@ -140,7 +140,7 @@ export function App() { (acc, curr) => { // note: the hilite list may contain models and subcategories as well - we don't // care about them at this moment - acc.elements.push(...(curr.elements ?? [])); + acc.elements.push(...curr.elements); return acc; }, { elements: [] }, diff --git a/apps/test-app/frontend/src/components/properties-widget/PropertiesWidget.tsx b/apps/test-app/frontend/src/components/properties-widget/PropertiesWidget.tsx index e09dbda23..178fe07bf 100644 --- a/apps/test-app/frontend/src/components/properties-widget/PropertiesWidget.tsx +++ b/apps/test-app/frontend/src/components/properties-widget/PropertiesWidget.tsx @@ -372,7 +372,7 @@ function FavoriteFieldActionButton(props: { imodel: IModelConnection; field: Fie } }, [field, imodel]); const { value: isFieldFavorite } = useDebouncedAsyncValue( - useCallback(async () => field && Presentation.favoriteProperties.hasAsync(field, props.imodel, FAVORITES_SCOPE), [field, props.imodel]), + useCallback(async () => Presentation.favoriteProperties.hasAsync(field, props.imodel, FAVORITES_SCOPE), [field, props.imodel]), ); return (
diff --git a/apps/test-app/frontend/src/components/tree-widget/MultiDataSourceTree.tsx b/apps/test-app/frontend/src/components/tree-widget/MultiDataSourceTree.tsx index 349080053..bcabacf1f 100644 --- a/apps/test-app/frontend/src/components/tree-widget/MultiDataSourceTree.tsx +++ b/apps/test-app/frontend/src/components/tree-widget/MultiDataSourceTree.tsx @@ -451,9 +451,6 @@ function createRssHierarchyProvider(): HierarchyProvider & { getSearchPaths: (fi async getSearchPaths(searchText: string): Promise { const feed = await getFeed(); - if (!feed) { - return []; - } const paths = new Array(); if ((feed.title ?? "").toLocaleLowerCase().includes(searchText.toLocaleLowerCase())) { @@ -474,10 +471,6 @@ function createRssHierarchyProvider(): HierarchyProvider & { getSearchPaths: (fi async *getNodes({ parentNode }: GetHierarchyNodesProps): AsyncIterableIterator { const feed = await getFeed(); - if (!feed) { - return; - } - async function* generateNodes(): AsyncIterableIterator { if (!parentNode) { yield { diff --git a/apps/test-app/frontend/src/components/tree-widget/StatelessTree.tsx b/apps/test-app/frontend/src/components/tree-widget/StatelessTree.tsx index 685de4120..6e6c3d5e0 100644 --- a/apps/test-app/frontend/src/components/tree-widget/StatelessTree.tsx +++ b/apps/test-app/frontend/src/components/tree-widget/StatelessTree.tsx @@ -265,7 +265,7 @@ function Tree({ if (!filteringOptions) { return; } - filteringOptions?.setInstanceFilter(toGenericFilter(info)); + filteringOptions.setInstanceFilter(toGenericFilter(info)); setFilteringOptions(undefined); }} onClose={() => { @@ -305,8 +305,8 @@ function getHierarchyDefinition(props: Parameters { - return `THIS_IS_FORMATTED_${val ? JSON.stringify(val.value) : ""}_THIS_IS_FORMATTED`; +const customFormatter: IPrimitiveValueFormatter = async ({ value }) => { + return `THIS_IS_FORMATTED_${JSON.stringify(value)}_THIS_IS_FORMATTED`; }; function fromGenericFilter(descriptor: Descriptor, filter: GenericInstanceFilter): PresentationInstanceFilterInfo { diff --git a/apps/test-app/frontend/src/index.tsx b/apps/test-app/frontend/src/index.tsx index 038227d94..e5a86b878 100644 --- a/apps/test-app/frontend/src/index.tsx +++ b/apps/test-app/frontend/src/index.tsx @@ -48,17 +48,12 @@ async function initializeApp() { BentleyCloudRpcManager.initializeClient(rpcParams, rpcInterfaces); // __PUBLISH_EXTRACT_END__ } - const readyPromises = new Array>(); - - const namespacePromise = IModelApp.localization.registerNamespace("Sample"); - if (namespacePromise !== undefined) { - readyPromises.push(namespacePromise); - } - - readyPromises.push(initializePresentation()); - readyPromises.push(UiFramework.initialize()); - readyPromises.push(IModelApp.quantityFormatter.setActiveUnitSystem("metric")); - await Promise.all(readyPromises); + await Promise.all([ + IModelApp.localization.registerNamespace("Sample"), + initializePresentation(), + UiFramework.initialize(), + IModelApp.quantityFormatter.setActiveUnitSystem("metric"), + ]); } async function initializePresentation() { diff --git a/eslint.base.config.js b/eslint.base.config.js index ab80a2a2e..d81b9747a 100644 --- a/eslint.base.config.js +++ b/eslint.base.config.js @@ -22,6 +22,12 @@ module.exports = [ ], "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-unsafe-enum-comparison": "off", + "@typescript-eslint/no-unnecessary-condition": [ + "error", + { + allowConstantLoopConditions: "only-allowed-literals", + }, + ], "@typescript-eslint/restrict-template-expressions": [ "error", { diff --git a/packages/components/src/presentation-components/common/ContentBuilder.ts b/packages/components/src/presentation-components/common/ContentBuilder.ts index fcf36ff55..caad01062 100644 --- a/packages/components/src/presentation-components/common/ContentBuilder.ts +++ b/packages/components/src/presentation-components/common/ContentBuilder.ts @@ -164,10 +164,10 @@ export interface INestedPropertiesAppender extends IPropertiesAppender { /** @internal */ export namespace IPropertiesAppender { export function isRoot(appender: IPropertiesAppender): appender is IRootPropertiesAppender { - return (appender as IRootPropertiesAppender).item !== undefined; + return "item" in appender; } export function isNested(appender: IPropertiesAppender): appender is INestedPropertiesAppender { - return (appender as INestedPropertiesAppender).finish !== undefined; + return "finish" in appender; } } class StructMembersAppender implements INestedPropertiesAppender { @@ -246,7 +246,6 @@ export class InternalPropertyRecordsBuilder implements IContentVisitor { protected get currentPropertiesAppender(): IPropertiesAppender { const appender = this._appendersStack[this._appendersStack.length - 1]; - assert(appender !== undefined); return appender; } diff --git a/packages/components/src/presentation-components/common/ContentDataProvider.ts b/packages/components/src/presentation-components/common/ContentDataProvider.ts index a2224545f..73fb633f2 100644 --- a/packages/components/src/presentation-components/common/ContentDataProvider.ts +++ b/packages/components/src/presentation-components/common/ContentDataProvider.ts @@ -289,19 +289,19 @@ export class ContentDataProvider implements IContentDataProvider { * Invalidates cached content. */ protected invalidateCache(props: CacheInvalidationProps): void { - if (props.descriptor && this.getDefaultContentDescriptor) { + if (props.descriptor) { this.getDefaultContentDescriptor.cache.keys.length = 0; this.getDefaultContentDescriptor.cache.values.length = 0; } - if (props.descriptorConfiguration && this.getContentDescriptor) { + if (props.descriptorConfiguration) { this.getContentDescriptor.cache.keys.length = 0; this.getContentDescriptor.cache.values.length = 0; } - if ((props.content || props.size) && this._getContentAndSize) { + if (props.content || props.size) { this._getContentAndSize.cache.keys.length = 0; this._getContentAndSize.cache.values.length = 0; } - if ((props.formatting || props.content || props.size) && this._getFormattedContentAndSize) { + if (props.formatting || props.content || props.size) { this._getFormattedContentAndSize.cache.keys.length = 0; this._getFormattedContentAndSize.cache.values.length = 0; this._isContentFormatted = false; @@ -325,6 +325,8 @@ export class ContentDataProvider implements IContentDataProvider { this._listeners.push(Presentation.presentation.rulesets().onRulesetModified.addListener(this.onRulesetModified)); this._listeners.push(Presentation.presentation.vars(getRulesetId(this._ruleset)).onVariableChanged.addListener(this.onRulesetVariableChanged)); this._listeners.push(IModelApp.quantityFormatter.onActiveFormattingUnitSystemChanged.addListener(this.onUnitSystemChanged)); + // note: IModelApp.formatsProvider may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition IModelApp.formatsProvider && this._listeners.push(IModelApp.formatsProvider.onFormatsChanged.addListener(this.onFormatsChanged)); } @@ -452,6 +454,8 @@ export class ContentDataProvider implements IContentDataProvider { }, }; + // note: `getContentIterator` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Presentation.presentation.getContentIterator) { const result = await Presentation.presentation.getContentIterator(options); return result diff --git a/packages/components/src/presentation-components/common/Utils.ts b/packages/components/src/presentation-components/common/Utils.ts index beb68f55b..e4fa85146 100644 --- a/packages/components/src/presentation-components/common/Utils.ts +++ b/packages/components/src/presentation-components/common/Utils.ts @@ -65,6 +65,8 @@ export const findField = (descriptor: Descriptor, recordPropertyName: string): F } if (field.isNestedContentField()) { fieldsSource = field; + // note: `isStructPropertiesField` and `isArrayPropertiesField` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (field.isPropertiesField() && (field.isStructPropertiesField?.() || field.isArrayPropertiesField?.())) { fieldsSource = field; } else { diff --git a/packages/components/src/presentation-components/instance-filter-builder/PresentationInstanceFilter.ts b/packages/components/src/presentation-components/instance-filter-builder/PresentationInstanceFilter.ts index 3d47b172a..e7139df9b 100644 --- a/packages/components/src/presentation-components/instance-filter-builder/PresentationInstanceFilter.ts +++ b/packages/components/src/presentation-components/instance-filter-builder/PresentationInstanceFilter.ts @@ -216,7 +216,7 @@ function createGenericInstanceFilterUniqueValueRules(filter: PresentationInstanc if (filter.operator !== "is-equal" && filter.operator !== "is-not-equal") { return undefined; } - if (typeof filter.value?.value !== "string" || typeof filter.value?.displayValue !== "string") { + if (typeof filter.value?.value !== "string" || typeof filter.value.displayValue !== "string") { return undefined; } const result = createUniqueValueConditions(filter, filter.value.displayValue, filter.value.value); @@ -400,7 +400,7 @@ function parseUniqueValuesRule(rules: GenericInstanceFilterRule[], ctx: GenericF const uniqueValues: UniqueValue[] = []; for (const rule of rules) { - assert(rule.value?.displayValue !== undefined && rule.value.rawValue !== undefined); + assert(rule.value?.displayValue !== undefined); const displayValue = rule.value.displayValue; const value = rule.value.rawValue as Value; diff --git a/packages/components/src/presentation-components/labels/LabelsProvider.ts b/packages/components/src/presentation-components/labels/LabelsProvider.ts index 485eb6c70..ac28fadd2 100644 --- a/packages/components/src/presentation-components/labels/LabelsProvider.ts +++ b/packages/components/src/presentation-components/labels/LabelsProvider.ts @@ -71,6 +71,8 @@ export class PresentationLabelsProvider implements IPresentationLabelsProvider { .pipe( bufferCount(DEFAULT_KEYS_BATCH_SIZE), mergeMap((keysBatch, batchIndex) => { + // note: `getDisplayLabelDefinitionsIterator` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Presentation.presentation.getDisplayLabelDefinitionsIterator) { return from(Presentation.presentation.getDisplayLabelDefinitionsIterator({ imodel: this.imodel, keys: keysBatch })).pipe( mergeMap((result) => result.items), diff --git a/packages/components/src/presentation-components/properties/inputs/QuantityPropertyEditorInput.tsx b/packages/components/src/presentation-components/properties/inputs/QuantityPropertyEditorInput.tsx index a35c91e0b..7a8d014bb 100644 --- a/packages/components/src/presentation-components/properties/inputs/QuantityPropertyEditorInput.tsx +++ b/packages/components/src/presentation-components/properties/inputs/QuantityPropertyEditorInput.tsx @@ -30,7 +30,8 @@ export const QuantityPropertyEditorInput = forwardRef ); diff --git a/packages/components/src/presentation-components/properties/inputs/UseNavigationPropertyTargetsLoader.ts b/packages/components/src/presentation-components/properties/inputs/UseNavigationPropertyTargetsLoader.ts index 5747fdebd..feb9f0fb8 100644 --- a/packages/components/src/presentation-components/properties/inputs/UseNavigationPropertyTargetsLoader.ts +++ b/packages/components/src/presentation-components/properties/inputs/UseNavigationPropertyTargetsLoader.ts @@ -132,7 +132,7 @@ export function useNavigationPropertyTargetsRuleset( const [ruleset, setRuleset] = useState(); useEffect(() => { - let disposed = false; + let disposed = false as boolean; void (async () => { const propertyInfo = await getNavigationPropertyInfo(property); if (!disposed && propertyInfo) { @@ -177,6 +177,8 @@ async function getItems(imodel: IModelConnection, ruleset: Ruleset, filter?: str paging: { size: VALUE_BATCH_SIZE }, }; const items = await new Promise((resolve, reject) => { + // note: `getContentIterator` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition (Presentation.presentation.getContentIterator ? from(Presentation.presentation.getContentIterator(requestProps)).pipe( mergeMap((result) => (result ? result.items : EMPTY)), diff --git a/packages/components/src/presentation-components/properties/inputs/UseQuantityValueInput.tsx b/packages/components/src/presentation-components/properties/inputs/UseQuantityValueInput.tsx index fc519ad0c..478fc341d 100644 --- a/packages/components/src/presentation-components/properties/inputs/UseQuantityValueInput.tsx +++ b/packages/components/src/presentation-components/properties/inputs/UseQuantityValueInput.tsx @@ -148,6 +148,8 @@ function useFormatterAndParser(koqName: string, schemaContext: SchemaContext) { void findFormatterAndParser(); const listeners = [IModelApp.quantityFormatter.onActiveFormattingUnitSystemChanged.addListener(findFormatterAndParser)]; + // note: `IModelApp.formatsProvider` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (IModelApp.formatsProvider) { listeners.push(IModelApp.formatsProvider.onFormatsChanged.addListener(findFormatterAndParser)); } diff --git a/packages/components/src/presentation-components/properties/inputs/UseUniquePropertyValuesLoader.ts b/packages/components/src/presentation-components/properties/inputs/UseUniquePropertyValuesLoader.ts index 79209ced7..d5a26c455 100644 --- a/packages/components/src/presentation-components/properties/inputs/UseUniquePropertyValuesLoader.ts +++ b/packages/components/src/presentation-components/properties/inputs/UseUniquePropertyValuesLoader.ts @@ -154,6 +154,8 @@ async function getItems({ keys, }; const items = await new Promise((resolve) => { + // note: `getDistinctValuesIterator` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition (Presentation.presentation.getDistinctValuesIterator ? from(Presentation.presentation.getDistinctValuesIterator(requestProps)).pipe( mergeMap((result) => result.items), diff --git a/packages/components/src/presentation-components/propertygrid/DataProvider.ts b/packages/components/src/presentation-components/propertygrid/DataProvider.ts index dfaf87cba..4f5739d51 100644 --- a/packages/components/src/presentation-components/propertygrid/DataProvider.ts +++ b/packages/components/src/presentation-components/propertygrid/DataProvider.ts @@ -375,6 +375,8 @@ export class PresentationPropertyDataProvider extends ContentDataProvider implem } async function isFieldFavorite(field: Field, imodel: IModelConnection) { + // note: `Presentation.favoriteProperties.hasAsync` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Presentation.favoriteProperties.hasAsync) { return Presentation.favoriteProperties.hasAsync(field, imodel, FavoritePropertiesScope.IModel); } @@ -383,6 +385,8 @@ async function isFieldFavorite(field: Field, imodel: IModelConnection) { } async function sortFavoriteFields(fields: Field[], imodel: IModelConnection) { + // note: `Presentation.favoriteProperties.sortFieldsAsync` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Presentation.favoriteProperties.sortFieldsAsync) { await Presentation.favoriteProperties.sortFieldsAsync(imodel, fields); return; @@ -632,7 +636,7 @@ class PropertyDataBuilder extends InternalPropertyRecordsBuilder { } public override processPrimitiveValue(props: ProcessPrimitiveValueProps): void { - if (this.shouldSkipField(props.field, () => null === props.rawValue || undefined === props.rawValue || "" === props.rawValue)) { + if (this.shouldSkipField(props.field, () => undefined === props.rawValue || "" === props.rawValue)) { return; } @@ -796,6 +800,7 @@ function destructureStructArrayItems(items: PropertyRecord[], fieldHierarchy: Fi items.forEach((item) => { assert(item.value.valueFormat === UiPropertyValueFormat.Struct); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (item.value.members[nestedFieldHierarchy.field.name] === undefined) { // the member may not exist at all if we decided to skip it beforehand return; @@ -855,6 +860,7 @@ function destructureRecords(records: FieldHierarchyRecord[]) { // destructure structs by replacing them with their member records const members = entry.fieldHierarchy.childFields.reduce((list, nestedFieldHierarchy) => { assert(entry.record.value.valueFormat === UiPropertyValueFormat.Struct); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition assert(entry.record.value.members[nestedFieldHierarchy.field.name] !== undefined); const member = { fieldHierarchy: nestedFieldHierarchy, diff --git a/packages/components/src/presentation-components/propertygrid/FavoritePropertiesDataFilterer.ts b/packages/components/src/presentation-components/propertygrid/FavoritePropertiesDataFilterer.ts index 29d46d6c2..168009a51 100644 --- a/packages/components/src/presentation-components/propertygrid/FavoritePropertiesDataFilterer.ts +++ b/packages/components/src/presentation-components/propertygrid/FavoritePropertiesDataFilterer.ts @@ -91,6 +91,8 @@ export class FavoritePropertiesDataFilterer extends PropertyDataFiltererBase { } async function defaultFavoritePropertyCheckCallback(field: Field, imodel: IModelConnection, scope: FavoritePropertiesScope) { + // note: `Presentation.favoriteProperties.hasAsync` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Presentation.favoriteProperties.hasAsync) { return Presentation.favoriteProperties.hasAsync(field, imodel, scope); } diff --git a/packages/components/src/presentation-components/table/UseColumns.ts b/packages/components/src/presentation-components/table/UseColumns.ts index bba7d79bd..2c85a4761 100644 --- a/packages/components/src/presentation-components/table/UseColumns.ts +++ b/packages/components/src/presentation-components/table/UseColumns.ts @@ -27,7 +27,7 @@ export function useColumns(props: UseColumnsProps): TableColumnDefinition[] | un const setErrorState = useErrorState(); useEffect(() => { - let disposed = false; + let disposed = false as boolean; if (keys.isEmpty) { setColumns([]); return; diff --git a/packages/components/src/presentation-components/table/UsePresentationTable.ts b/packages/components/src/presentation-components/table/UsePresentationTable.ts index 9c7a6a23d..da5a6d1a2 100644 --- a/packages/components/src/presentation-components/table/UsePresentationTable.ts +++ b/packages/components/src/presentation-components/table/UsePresentationTable.ts @@ -71,7 +71,7 @@ export function usePresentationTable(props: UsePresentationTableP return { columns: useMemo(() => columns?.map(columnMapper), [columns, columnMapper]), - rows: useMemo(() => rows?.map(rowMapper), [rows, rowMapper]), + rows: useMemo(() => rows.map(rowMapper), [rows, rowMapper]), isLoading, loadMoreRows, sort, diff --git a/packages/components/src/presentation-components/table/UseRows.ts b/packages/components/src/presentation-components/table/UseRows.ts index 5dd08bb7e..0d11bde5c 100644 --- a/packages/components/src/presentation-components/table/UseRows.ts +++ b/packages/components/src/presentation-components/table/UseRows.ts @@ -217,6 +217,8 @@ async function loadRows( paging, }; return new Promise((resolve, reject) => { + // note: `getContentIterator` may not be available in older versions of core + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition (Presentation.presentation.getContentIterator ? from(Presentation.presentation.getContentIterator(requestProps)).pipe( mergeMap((result) => { diff --git a/packages/components/src/presentation-components/tree/DataProvider.ts b/packages/components/src/presentation-components/tree/DataProvider.ts index 4b53556f2..ee38dae60 100644 --- a/packages/components/src/presentation-components/tree/DataProvider.ts +++ b/packages/components/src/presentation-components/tree/DataProvider.ts @@ -153,6 +153,7 @@ export class PresentationTreeDataProvider implements IPresentationTreeDataProvid // the `PresentationManager.getNodesIterator` has only been added to @itwin/presentation-frontend in 4.5.1, and our peerDependency is // set to 4.0.0, so we need to check if the method is really there + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Presentation.presentation.getNodesIterator) { return Presentation.presentation.getNodesIterator(requestOptions); } diff --git a/packages/components/src/presentation-components/tree/PresentationTreeNodeItem.ts b/packages/components/src/presentation-components/tree/PresentationTreeNodeItem.ts index af9f510f0..7ccf4068f 100644 --- a/packages/components/src/presentation-components/tree/PresentationTreeNodeItem.ts +++ b/packages/components/src/presentation-components/tree/PresentationTreeNodeItem.ts @@ -104,6 +104,7 @@ export type FilterablePresentationTreeNodeItem = PresentationTreeNodeItem & { * building APIs (see https://github.com/iTwin/presentation/blob/33e79ee8d77f30580a9bab81a72884bda008db25/README.md#the-packages). */ export function isPresentationTreeNodeItem(item: TreeNodeItem): item is PresentationTreeNodeItem { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition return (item as PresentationTreeNodeItem).key !== undefined; } @@ -114,6 +115,7 @@ export function isPresentationTreeNodeItem(item: TreeNodeItem): item is Presenta * building APIs (see https://github.com/iTwin/presentation/blob/33e79ee8d77f30580a9bab81a72884bda008db25/README.md#the-packages). */ export function isPresentationInfoTreeNodeItem(item: TreeNodeItem): item is PresentationInfoTreeNodeItem { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition return (item as PresentationInfoTreeNodeItem).message !== undefined; } diff --git a/packages/components/src/presentation-components/tree/controlled/TreeReloader.ts b/packages/components/src/presentation-components/tree/controlled/TreeReloader.ts index 2ed5e8f47..848fcc540 100644 --- a/packages/components/src/presentation-components/tree/controlled/TreeReloader.ts +++ b/packages/components/src/presentation-components/tree/controlled/TreeReloader.ts @@ -117,7 +117,6 @@ class TreeReloader extends PagedTreeNodeLoader { take(1), // If the node is found, load and expand its children recursively concatMap((loadedNode) => { - assert(loadedNode !== undefined); return concat(this.loadChildren(loadedNode), expandedNode.expandedChildren); }), ); diff --git a/packages/components/src/test/common/ContentDataProvider.test.ts b/packages/components/src/test/common/ContentDataProvider.test.ts index 10f1d3489..ceea6f0a8 100644 --- a/packages/components/src/test/common/ContentDataProvider.test.ts +++ b/packages/components/src/test/common/ContentDataProvider.test.ts @@ -428,7 +428,7 @@ describe("ContentDataProvider", () => { if (!options.paging?.start && !options.paging?.size) { return resultNoPageOptions; } - if (!options.paging?.start && options.paging.size) { + if (!options.paging.start && options.paging.size) { return resultNoPageStartWithSize; } if (options.paging.start) { @@ -840,7 +840,7 @@ describe("ContentDataProvider", () => { await provider.getContentSetSize(); expect(presentationManager.getContentIterator).to.be.calledOnceWith( - matchOptions((options) => options.diagnostics?.editor === "error" && options.diagnostics?.handler === diagnosticsHandler), + matchOptions((options) => options.diagnostics?.editor === "error" && options.diagnostics.handler === diagnosticsHandler), ); }); @@ -873,9 +873,9 @@ describe("ContentDataProvider", () => { matchOptions( (options) => options.diagnostics?.backendVersion === true && - options.diagnostics?.perf === true && - options.diagnostics?.dev === "error" && - options.diagnostics?.handler === diagnosticsHandler, + options.diagnostics.perf === true && + options.diagnostics.dev === "error" && + options.diagnostics.handler === diagnosticsHandler, ), ); }); diff --git a/packages/components/src/test/table/UseRows.test.tsx b/packages/components/src/test/table/UseRows.test.tsx index 5818f1242..d54368027 100644 --- a/packages/components/src/test/table/UseRows.test.tsx +++ b/packages/components/src/test/table/UseRows.test.tsx @@ -321,7 +321,7 @@ describe("useRows", () => { await waitFor(() => expect(result.current.rows).to.have.lengthOf(2)); // initial rows load request expect(getContentIteratorStub).to.be.calledWith( - sinon.match(({ paging }: Parameters[0]) => paging?.start === 0 && paging?.size === 10), + sinon.match(({ paging }: Parameters[0]) => paging?.start === 0 && paging.size === 10), ); act(() => { @@ -332,7 +332,7 @@ describe("useRows", () => { expect(result.current.rows).to.have.lengthOf(2); // reload request should have page options to get only previously loaded rows. expect(getContentIteratorStub).to.be.calledWith( - sinon.match(({ paging }: Parameters[0]) => paging?.start === 0 && paging?.size === 2), + sinon.match(({ paging }: Parameters[0]) => paging?.start === 0 && paging.size === 2), ); }); }); @@ -355,7 +355,7 @@ describe("useRows", () => { // initial load request expect(getContentIteratorStub).to.be.calledWith( - sinon.match(({ paging }: Parameters[0]) => paging?.start === 0 && paging?.size === 10), + sinon.match(({ paging }: Parameters[0]) => paging?.start === 0 && paging.size === 10), ); getContentIteratorStub.resetHistory(); @@ -395,10 +395,10 @@ describe("useRows", () => { // setup presentation manager for rows reload getContentIteratorStub.reset(); getContentIteratorStub.callsFake(async (options) => { - if (options.paging?.start === 0 && options.paging?.size === ROWS_RELOAD_PAGE_SIZE) { + if (options.paging?.start === 0 && options.paging.size === ROWS_RELOAD_PAGE_SIZE) { return { descriptor, items: createAsyncIterator(items.slice(0, ROWS_RELOAD_PAGE_SIZE)), total: ROWS_RELOAD_PAGE_SIZE }; } - if (options.paging?.start === ROWS_RELOAD_PAGE_SIZE && options.paging?.size === 1) { + if (options.paging?.start === ROWS_RELOAD_PAGE_SIZE && options.paging.size === 1) { return { descriptor, items: createAsyncIterator(items.slice(ROWS_RELOAD_PAGE_SIZE)), total: 1 }; } return undefined; @@ -411,10 +411,10 @@ describe("useRows", () => { await waitFor(() => { expect(result.current.rows).to.have.lengthOf(itemsCount); expect(getContentIteratorStub).to.be.calledWith( - sinon.match(({ paging }: Parameters[0]) => paging?.start === 0 && paging?.size === ROWS_RELOAD_PAGE_SIZE), + sinon.match(({ paging }: Parameters[0]) => paging?.start === 0 && paging.size === ROWS_RELOAD_PAGE_SIZE), ); expect(getContentIteratorStub).to.be.calledWith( - sinon.match(({ paging }: Parameters[0]) => paging?.start === ROWS_RELOAD_PAGE_SIZE && paging?.size === 1), + sinon.match(({ paging }: Parameters[0]) => paging?.start === ROWS_RELOAD_PAGE_SIZE && paging.size === 1), ); }); }); diff --git a/packages/components/src/test/tree/DataProvider.test.ts b/packages/components/src/test/tree/DataProvider.test.ts index f6e05ad5c..4f620da0d 100644 --- a/packages/components/src/test/tree/DataProvider.test.ts +++ b/packages/components/src/test/tree/DataProvider.test.ts @@ -9,7 +9,7 @@ import { createAsyncIterator, ResolvablePromise } from "presentation-test-utilit import * as sinon from "sinon"; import { PropertyRecord } from "@itwin/appui-abstract"; import { CheckBoxState, PageOptions } from "@itwin/components-react"; -import { assert, BeEvent, Logger } from "@itwin/core-bentley"; +import { BeEvent, Logger } from "@itwin/core-bentley"; import { EmptyLocalization } from "@itwin/core-common"; import { IModelConnection } from "@itwin/core-frontend"; import { @@ -580,7 +580,7 @@ describe("TreeDataProvider", () => { expect(presentationManager.getNodesIterator).to.be.calledWith( matchOptions( ({ diagnostics }) => - diagnostics?.backendVersion === true && diagnostics.perf === true && diagnostics?.dev === "error" && diagnostics.handler === diagnosticsHandler, + diagnostics?.backendVersion === true && diagnostics.perf === true && diagnostics.dev === "error" && diagnostics.handler === diagnosticsHandler, ), ); }); @@ -588,7 +588,6 @@ describe("TreeDataProvider", () => { describe("filterable nodes", () => { async function loadDescriptor(filteringInfo: PresentationTreeNodeItemFilteringInfo) { - assert(filteringInfo.descriptor !== undefined); if (filteringInfo.descriptor instanceof Descriptor) { return filteringInfo.descriptor; } diff --git a/packages/core-interop/src/core-interop/MetadataInternal.ts b/packages/core-interop/src/core-interop/MetadataInternal.ts index 9d93b41d7..9b85041a5 100644 --- a/packages/core-interop/src/core-interop/MetadataInternal.ts +++ b/packages/core-interop/src/core-interop/MetadataInternal.ts @@ -115,7 +115,8 @@ abstract class ECClassImpl extends ECSchemaItemImp // `SchemaFormatsProvider` was introduced around the same time the meaning of this second argument was changed // from `includeInherited` to `excludeInherited` - we're using its existence to determine what we need to pass to get // inherited properties. - /* c8 ignore next */ + /* c8 ignore next 2 */ + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition ecschemaMetadata.SchemaFormatsProvider ? false : true, ); return coreProperty ? createECProperty(coreProperty, this) : undefined; @@ -408,15 +409,10 @@ class ECRelationshipConstraintImpl implements EC.RelationshipConstraint { private _schema: EC.Schema, ) {} public get multiplicity() { - return this._coreConstraint.multiplicity - ? { - lowerLimit: this._coreConstraint.multiplicity.lowerLimit, - upperLimit: this._coreConstraint.multiplicity.upperLimit, - } - : undefined; + return this._coreConstraint.multiplicity; } public get polymorphic() { - return this._coreConstraint.polymorphic ?? false; + return this._coreConstraint.polymorphic; } public get abstractConstraint(): Promise { return createFromOptionalLazyLoaded(this._coreConstraint.abstractConstraint, (coreConstraint) => createECClass(coreConstraint, this._schema)); diff --git a/packages/core-interop/src/core-interop/QueryExecutor.ts b/packages/core-interop/src/core-interop/QueryExecutor.ts index 893a3bcb8..adb7de82b 100644 --- a/packages/core-interop/src/core-interop/QueryExecutor.ts +++ b/packages/core-interop/src/core-interop/QueryExecutor.ts @@ -50,7 +50,7 @@ export function createECSqlQueryExecutor(imodel: CoreECSqlReaderFactory): ECSqlQ break; } if (config?.restartToken) { - opts.setRestartToken(config?.restartToken); + opts.setRestartToken(config.restartToken); } return new ECSqlQueryReaderImpl( imodel.createQueryReader(trimWhitespace(addCTEs(ecsql, ctes)), bind(bindings ?? []), opts.getOptions()), diff --git a/packages/core-interop/src/test/Metadata.test.ts b/packages/core-interop/src/test/Metadata.test.ts index c95d73cd6..92a697819 100644 --- a/packages/core-interop/src/test/Metadata.test.ts +++ b/packages/core-interop/src/test/Metadata.test.ts @@ -160,7 +160,6 @@ describe("createECSchema", () => { const schema = createECSchema(coreSchema); const result = await schema.getCustomAttributes(); - assert(result !== undefined); const entries = [...result]; expect(entries.length).to.eq(1); @@ -176,7 +175,6 @@ describe("createECSchema", () => { const schema = createECSchema(coreSchema); const result = await schema.getCustomAttributes(); - assert(result !== undefined); const entries = [...result]; expect(entries.length).to.eq(0); @@ -474,7 +472,6 @@ describe("createECClass", () => { } as unknown as CoreClass; const ecClass = createECClass(coreClass, schema); const result = await ecClass.getCustomAttributes(); - assert(result !== undefined); const entries = [...result]; expect(entries.length).to.eq(1); @@ -492,7 +489,6 @@ describe("createECClass", () => { } as unknown as CoreClass; const ecClass = createECClass(coreClass, schema); const result = await ecClass.getCustomAttributes(); - assert(result !== undefined); const entries = [...result]; expect(entries.length).to.eq(0); @@ -553,7 +549,6 @@ describe("createECClass", () => { it("returns polymorphic flag from core constraint", () => { [ - { in: undefined, expectation: false }, { in: false, expectation: false }, { in: true, expectation: true }, ].forEach((testEntry) => { @@ -607,7 +602,6 @@ describe("createECProperty", () => { } as unknown as CorePrimitiveProperty; const property = createECProperty(coreProperty, propertyClass); const result = await property.getCustomAttributes(); - assert(result !== undefined); const entries = [...result]; expect(entries.length).to.eq(1); @@ -624,7 +618,6 @@ describe("createECProperty", () => { } as unknown as CorePrimitiveProperty; const property = createECProperty(coreProperty, propertyClass); const result = await property.getCustomAttributes(); - assert(result !== undefined); const entries = [...result]; expect(entries.length).to.eq(0); diff --git a/packages/hierarchies-react/src/presentation-hierarchies-react/UseTree.ts b/packages/hierarchies-react/src/presentation-hierarchies-react/UseTree.ts index f28f8073c..337aad938 100644 --- a/packages/hierarchies-react/src/presentation-hierarchies-react/UseTree.ts +++ b/packages/hierarchies-react/src/presentation-hierarchies-react/UseTree.ts @@ -178,7 +178,7 @@ function useTreeInternal({ const [isSearching, setIsSearching] = useState(false); useEffect(() => { - let disposed = false; + let disposed = false as boolean; const controller = new AbortController(); void (async () => { if (!hierarchyProvider) { diff --git a/packages/hierarchies-react/src/presentation-hierarchies-react/internal/TreeActions.ts b/packages/hierarchies-react/src/presentation-hierarchies-react/internal/TreeActions.ts index 42b08816e..0fc86d331 100644 --- a/packages/hierarchies-react/src/presentation-hierarchies-react/internal/TreeActions.ts +++ b/packages/hierarchies-react/src/presentation-hierarchies-react/internal/TreeActions.ts @@ -229,7 +229,7 @@ export class TreeActions { } public expandNode(nodeId: string, isExpanded: boolean) { - let childrenAction: ReturnType = "none"; + let childrenAction = "none" as ReturnType; this.updateTreeModel((model) => { childrenAction = TreeModel.expandNode(model, nodeId, isExpanded); }); @@ -243,7 +243,7 @@ export class TreeActions { public setHierarchyLimit(nodeId: string | undefined, limit?: number | "unbounded") { const oldModel = this._currentModel; - let loadChildren = false; + let loadChildren = false as boolean; this.updateTreeModel((model) => { loadChildren = TreeModel.setHierarchyLimit(model, nodeId, limit); }); @@ -257,7 +257,7 @@ export class TreeActions { public setInstanceFilter(nodeId: string | undefined, filter?: GenericInstanceFilter) { const oldModel = this._currentModel; - let loadChildren = false; + let loadChildren = false as boolean; this.updateTreeModel((model) => { loadChildren = TreeModel.setInstanceFilter(model, nodeId, filter); }); @@ -274,7 +274,7 @@ export class TreeActions { this.updateTreeModel((model) => { TreeModel.setIsLoading(model, options?.parentNodeId, true); if (options?.state === "reset") { - TreeModel.removeSubTree(model, options?.parentNodeId); + TreeModel.removeSubTree(model, options.parentNodeId); } }); diff --git a/packages/hierarchies-react/src/presentation-hierarchies-react/internal/TreeLoader.ts b/packages/hierarchies-react/src/presentation-hierarchies-react/internal/TreeLoader.ts index d307ebca2..f9325cfc5 100644 --- a/packages/hierarchies-react/src/presentation-hierarchies-react/internal/TreeLoader.ts +++ b/packages/hierarchies-react/src/presentation-hierarchies-react/internal/TreeLoader.ts @@ -142,8 +142,7 @@ function createTreeModelNodesFactory({ } function isRowsLimitError(error: Error): error is RowsLimitExceededError { - const asRowsError = error as RowsLimitExceededError; - return asRowsError.limit !== undefined && asRowsError.message.includes("Query rows limit of"); + return "limit" in error && error.message.includes("Query rows limit of"); } function isTimeoutError(error: Error) { diff --git a/packages/hierarchies-react/src/presentation-hierarchies-react/internal/UseUnifiedSelection.ts b/packages/hierarchies-react/src/presentation-hierarchies-react/internal/UseUnifiedSelection.ts index cb025213c..cb657c7de 100644 --- a/packages/hierarchies-react/src/presentation-hierarchies-react/internal/UseUnifiedSelection.ts +++ b/packages/hierarchies-react/src/presentation-hierarchies-react/internal/UseUnifiedSelection.ts @@ -89,32 +89,35 @@ function createOptions( return { isNodeSelected: (nodeId: string) => { const node = getNode(nodeId); - if (!node || node?.id === undefined) { + if (!node || node.id === undefined) { return false; } - return Object.entries(groupNodeSelectablesByIModelKey(node, createSelectableForGenericNode)).some(([imodelKey, nodeSelectables]) => { + for (const [imodelKey, nodeSelectables] of groupNodeSelectablesByIModelKey(node, createSelectableForGenericNode)) { const storageSelectables = storage.getSelection({ imodelKey, level: 0 }); - return Selectables.hasAny(storageSelectables, nodeSelectables); - }); + if (Selectables.hasAny(storageSelectables, nodeSelectables)) { + return true; + } + } + return false; }, selectNodes: (nodeIds: Array, changeType: SelectionChangeType) => { - const imodelSelectables: { [imodelKey: string]: Selectable[] } = {}; + const imodelSelectables = new Map(); for (const nodeId of nodeIds) { const node = getNode(nodeId); - if (!node || node?.id === undefined) { + if (!node || node.id === undefined) { return; } - Object.entries(groupNodeSelectablesByIModelKey(node, createSelectableForGenericNode)).forEach(([imodelKey, nodeSelectables]) => { - let selectablesList = imodelSelectables[imodelKey]; + for (const [imodelKey, nodeSelectables] of groupNodeSelectablesByIModelKey(node, createSelectableForGenericNode)) { + let selectablesList = imodelSelectables.get(imodelKey); if (!selectablesList) { selectablesList = []; - imodelSelectables[imodelKey] = selectablesList; + imodelSelectables.set(imodelKey, selectablesList); } nodeSelectables.forEach((selectable) => selectablesList.push(selectable)); - }); + } } - Object.entries(imodelSelectables).forEach(([imodelKey, selectables]) => { + imodelSelectables.forEach((selectables, imodelKey) => { const actionProps = { imodelKey, source, selectables, level: 0 }; switch (changeType) { case "add": @@ -135,49 +138,42 @@ function createOptions( function groupNodeSelectablesByIModelKey( modelNode: TreeModelHierarchyNode, createSelectableForGenericNode: NonNullable, -): { [imodelKey: string]: Selectable[] } { +): Map { const hierarchyNode = modelNode.nodeData; if (HierarchyNode.isInstancesNode(hierarchyNode)) { return groupIModelInstanceKeys(hierarchyNode.key.instanceKeys); } if (HierarchyNode.isGroupingNode(hierarchyNode)) { - return Object.entries(groupIModelInstanceKeys(hierarchyNode.groupedInstanceKeys)).reduce( - (imodelSelectables, [imodelKey, instanceKeys]) => ({ - ...imodelSelectables, - [imodelKey]: [ - { - identifier: modelNode.id, - data: hierarchyNode, - async *loadInstanceKeys() { - for (const key of instanceKeys) { - yield key; - } - }, + const groupingNodeSelectables = new Map(); + groupIModelInstanceKeys(hierarchyNode.groupedInstanceKeys).forEach((instanceKeys, imodelKey) => { + groupingNodeSelectables.set(imodelKey, [ + { + identifier: modelNode.id, + data: hierarchyNode, + async *loadInstanceKeys() { + for (const key of instanceKeys) { + yield key; + } }, - ], - }), - {} as { [imodelKey: string]: Selectable[] }, - ); + }, + ]); + }); + return groupingNodeSelectables; } assert(HierarchyNode.isGeneric(hierarchyNode)); - return { - // note: generic nodes aren't associated with an imodel - [""]: [createSelectableForGenericNode(hierarchyNode, modelNode.id)], - }; + // note: generic nodes aren't associated with an imodel + return new Map([["", [createSelectableForGenericNode(hierarchyNode, modelNode.id)]]]); } function groupIModelInstanceKeys(instanceKeys: InstancesNodeKey["instanceKeys"]) { - return instanceKeys.reduce( - (imodelSelectables, key) => { - const imodelKey = key.imodelKey ?? ""; - let selectablesList = imodelSelectables[imodelKey]; - if (!selectablesList) { - selectablesList = []; - imodelSelectables[imodelKey] = selectablesList; - } - selectablesList.push(key); - return imodelSelectables; - }, - {} as { [imodelKey: string]: InstanceKey[] }, - ); + return instanceKeys.reduce((imodelSelectables, key) => { + const imodelKey = key.imodelKey ?? ""; + let selectablesList = imodelSelectables.get(imodelKey); + if (!selectablesList) { + selectablesList = []; + imodelSelectables.set(imodelKey, selectablesList); + } + selectablesList.push(key); + return imodelSelectables; + }, new Map()); } diff --git a/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/ErrorItemRenderer.tsx b/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/ErrorItemRenderer.tsx index c4b1a4ae4..080fca9c8 100644 --- a/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/ErrorItemRenderer.tsx +++ b/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/ErrorItemRenderer.tsx @@ -37,25 +37,25 @@ export function ErrorItemRenderer({ errorNode, getHierarchyLevelDetails, filterH if (errorNode.error.type === "ResultSetTooLarge") { const limit = errorNode.error.resultSetSizeLimit; - const onOverrideLimit = getHierarchyLevelDetails ? () => getHierarchyLevelDetails(errorNode.id)?.setSizeLimit(MAX_LIMIT_OVERRIDE) : undefined; + const onOverrideLimit = () => getHierarchyLevelDetails(errorNode.id)?.setSizeLimit(MAX_LIMIT_OVERRIDE); return ( { - onOverrideLimit?.(); + onOverrideLimit(); }, label: localizedStrings.increaseHierarchyLimit.replace("{{limit}}", MAX_LIMIT_OVERRIDE.toString()), - condition: () => !!onOverrideLimit && limit < MAX_LIMIT_OVERRIDE, + condition: () => limit < MAX_LIMIT_OVERRIDE, }, { action: () => { - const hierarchyLevelDetails = getHierarchyLevelDetails?.(errorNode.id); + const hierarchyLevelDetails = getHierarchyLevelDetails(errorNode.id); hierarchyLevelDetails && filterHierarchyLevel?.(hierarchyLevelDetails); }, label: localizedStrings.increaseHierarchyLimitWithFiltering, - condition: () => !!filterHierarchyLevel && !!errorNode?.isFilterable, + condition: () => !!filterHierarchyLevel && !!errorNode.isFilterable, }, ]} message={localizedStrings.resultLimitExceeded.replace("{{limit}}", limit.toString())} @@ -70,7 +70,7 @@ export function ErrorItemRenderer({ errorNode, getHierarchyLevelDetails, filterH actions={[ { action: () => { - const hierarchyLevelDetails = getHierarchyLevelDetails?.(errorNode.id); + const hierarchyLevelDetails = getHierarchyLevelDetails(errorNode.id); hierarchyLevelDetails && filterHierarchyLevel?.(hierarchyLevelDetails); }, label: localizedStrings.noFilteredChildrenChangeFilter, diff --git a/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeNodeFilterAction.tsx b/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeNodeFilterAction.tsx index 48ab419cd..e24f240c6 100644 --- a/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeNodeFilterAction.tsx +++ b/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeNodeFilterAction.tsx @@ -37,7 +37,7 @@ export const TreeNodeFilterAction = memo(function TreeNodeFilterAction({ const { filterHierarchyLevel, filterHierarchyLevelActiveDescription } = localizedStrings; const handleClick = useCallback(() => { - const hierarchyLevelDetails = getHierarchyLevelDetails?.(node.id); + const hierarchyLevelDetails = getHierarchyLevelDetails(node.id); hierarchyLevelDetails && onFilter?.(hierarchyLevelDetails); }, [node, getHierarchyLevelDetails, onFilter]); diff --git a/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeRenderer.tsx b/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeRenderer.tsx index 77d0d5bc1..427ce18aa 100644 --- a/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeRenderer.tsx +++ b/packages/hierarchies-react/src/presentation-hierarchies-react/stratakit/TreeRenderer.tsx @@ -121,7 +121,7 @@ export const StrataKitTreeRenderer: FC { - let calculatedSelectedNodes: TreeNode[]; + let calculatedSelectedNodes: TreeNode[] | undefined; return () => { if (calculatedSelectedNodes === undefined) { calculatedSelectedNodes = flatItems - .filter((item): item is FlatTreeNodeItem => !isPlaceholderItem(item) && isNodeSelected?.(item.id)) + .filter((item): item is FlatTreeNodeItem => !isPlaceholderItem(item) && isNodeSelected(item.id)) .map((item) => item.node); } return calculatedSelectedNodes; @@ -194,7 +194,7 @@ export const StrataKitTreeRenderer: FC {items.map((virtualizedItem) => { const item = flatItems[virtualizedItem.index]; - const selected = isNodeSelected?.(item.id) ?? false; + const selected = isNodeSelected(item.id); return ( { const rootNode2 = createTestHierarchyNode({ id: "root-2" }); hierarchyProvider.getNodes.callsFake(() => { - const activePaths = hierarchyProvider.setHierarchySearch.lastCall?.args[0]?.paths; + const activePaths = hierarchyProvider.setHierarchySearch.lastCall.args[0]?.paths; if (activePaths === paths1) { return createAsyncIterator([rootNode1]); } @@ -661,10 +661,7 @@ describe("useTree", () => { if (props.parentNode === undefined) { return createAsyncIterator(rootNodes); } - if (props.parentNode !== undefined) { - return createAsyncIterator(childNodes); - } - return createAsyncIterator([]); + return createAsyncIterator(childNodes); }); const { result } = renderHook(useTree, { initialProps }); const nodeId = createNodeId(rootNodes[0]); @@ -715,10 +712,7 @@ describe("useTree", () => { if (props.parentNode === undefined) { return createAsyncIterator(rootNodes); } - if (props.parentNode !== undefined) { - return createAsyncIterator(childNodes.slice(0, 1)); - } - return createAsyncIterator([]); + return createAsyncIterator(childNodes.slice(0, 1)); }); const { result } = renderHook(useTree, { initialProps }); @@ -733,10 +727,7 @@ describe("useTree", () => { if (props.parentNode === undefined) { return createAsyncIterator(rootNodes); } - if (props.parentNode !== undefined) { - return createAsyncIterator(childNodes); - } - return createAsyncIterator([]); + return createAsyncIterator(childNodes); }); act(() => { diff --git a/packages/hierarchies-react/src/test/internal/TreeActions.test.ts b/packages/hierarchies-react/src/test/internal/TreeActions.test.ts index e73e9f881..f0e5bd882 100644 --- a/packages/hierarchies-react/src/test/internal/TreeActions.test.ts +++ b/packages/hierarchies-react/src/test/internal/TreeActions.test.ts @@ -308,10 +308,10 @@ describe("TreeActions", () => { const actions = createActions(model); await Promise.all([ - actions.expandNode("root-1", true)?.complete, - actions.expandNode("root-2", true)?.complete, - actions.expandNode("root-1", false)?.complete, - actions.expandNode("root-1", true)?.complete, + actions.expandNode("root-1", true).complete, + actions.expandNode("root-2", true).complete, + actions.expandNode("root-1", false).complete, + actions.expandNode("root-1", true).complete, ]); expect(provider.getNodes).to.be.calledTwice; @@ -368,7 +368,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.expandNode("root-1", true)?.complete; + await actions.expandNode("root-1", true).complete; expect(onModelChangedStub).to.be.calledTwice; let newModel = onModelChangedStub.firstCall.args[0]; @@ -402,7 +402,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.expandNode("grouping-node", true)?.complete; + await actions.expandNode("grouping-node", true).complete; expect(provider.getNodes).to.be.calledWith( createGetNodesProps({ parentNode: getHierarchyNode(model, "grouping-node")?.nodeData, instanceFilter: filter, ignoreCache: false }), @@ -436,7 +436,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.expandNode("grouping-node", true)?.complete; + await actions.expandNode("grouping-node", true).complete; expect(provider.getNodes).to.be.calledWith( createGetNodesProps({ parentNode: getHierarchyNode(model, "grouping-node")?.nodeData, instanceFilter: filter, ignoreCache: false }), @@ -463,7 +463,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.setHierarchyLimit("root-1", 100)?.complete; + await actions.setHierarchyLimit("root-1", 100).complete; expect(onModelChangedStub).to.be.calledOnce; const newModel = onModelChangedStub.firstCall.args[0]; @@ -489,7 +489,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.setHierarchyLimit("root-1", 100)?.complete; + await actions.setHierarchyLimit("root-1", 100).complete; expect(onModelChangedStub).to.be.calledTwice; let newModel = onModelChangedStub.firstCall.args[0]; @@ -549,11 +549,11 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.setHierarchyLimit(undefined, 100)?.complete; + await actions.setHierarchyLimit(undefined, 100).complete; expect(onModelChangedStub).to.be.calledTwice; const newModel = onModelChangedStub.firstCall.args[0]; - expect(newModel.rootNode?.hierarchyLimit).to.be.eq(100); + expect(newModel.rootNode.hierarchyLimit).to.be.eq(100); await waitFor(() => { expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: newModel.rootNode.nodeData, hierarchyLevelSizeLimit: 100 })); @@ -586,7 +586,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.setInstanceFilter("root-1", filter)?.complete; + await actions.setInstanceFilter("root-1", filter).complete; () => expect(onModelChangedStub).to.be.calledTwice; const newModel = onModelChangedStub.firstCall.args[0]; @@ -622,7 +622,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.setInstanceFilter("root-A", filter)?.complete; + await actions.setInstanceFilter("root-A", filter).complete; expect(onModelChangedStub).to.be.calledTwice; let newModel = onModelChangedStub.firstCall.args[0]; @@ -664,7 +664,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.setInstanceFilter("root-1", filter)?.complete; + await actions.setInstanceFilter("root-1", filter).complete; expect(onModelChangedStub).to.be.calledTwice; let newModel = onModelChangedStub.firstCall.args[0]; @@ -696,11 +696,11 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.setInstanceFilter(undefined, filter)?.complete; + await actions.setInstanceFilter(undefined, filter).complete; expect(onModelChangedStub).to.be.calledTwice; let newModel = onModelChangedStub.firstCall.args[0]; - expect(newModel.rootNode?.instanceFilter).to.be.eq(filter); + expect(newModel.rootNode.instanceFilter).to.be.eq(filter); expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: newModel.rootNode.nodeData, instanceFilter: filter })); expect(onModelChangedStub).to.be.calledTwice; newModel = onModelChangedStub.secondCall.args[0]; @@ -726,7 +726,7 @@ describe("TreeActions", () => { ]); const actions = createActions(model); - await actions.setInstanceFilter("root-1", filter)?.complete; + await actions.setInstanceFilter("root-1", filter).complete; expect(onModelChangedStub).to.be.calledTwice; let newModel = onModelChangedStub.firstCall.args[0]; @@ -797,7 +797,7 @@ describe("TreeActions", () => { actions.reloadTree({ state: "keep" }); actions.reloadTree({ state: "keep" }); - await actions.reloadTree({ state: "keep" })?.complete; + await actions.reloadTree({ state: "keep" }).complete; expect(provider.getNodes).to.be.calledTwice; // once for root node, once for child node expect(onModelChangedStub).to.be.calledTwice; @@ -834,9 +834,9 @@ describe("TreeActions", () => { const actions = createActions(model); await Promise.all([ - actions.reloadTree({ state: "keep" })?.complete, - actions.reloadTree({ state: "discard" })?.complete, - actions.reloadTree({ state: "keep" })?.complete, + actions.reloadTree({ state: "keep" }).complete, + actions.reloadTree({ state: "discard" }).complete, + actions.reloadTree({ state: "keep" }).complete, ]); expect(provider.getNodes).to.be.calledOnce; // state discarded loaded only root node @@ -873,7 +873,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.reloadTree(undefined)?.complete; + await actions.reloadTree(undefined).complete; expect(provider.getNodes).to.be.calledTwice; expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: model.rootNode.nodeData, ignoreCache: false })); @@ -919,7 +919,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.reloadTree({ state: "keep" })?.complete; + await actions.reloadTree({ state: "keep" }).complete; expect(provider.getNodes).to.be.calledTwice; expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: model.rootNode.nodeData, ignoreCache: false })); @@ -963,7 +963,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.reloadTree(undefined)?.complete; + await actions.reloadTree(undefined).complete; expect(provider.getNodes).to.be.calledTwice; expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: model.rootNode.nodeData, ignoreCache: false })); @@ -1006,7 +1006,7 @@ describe("TreeActions", () => { const actions = createActions(model); expect(getHierarchyNode(model, "root-1")?.isExpanded).to.be.true; - await actions.reloadTree(undefined)?.complete; + await actions.reloadTree(undefined).complete; expect(provider.getNodes).to.be.calledTwice; expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: model.rootNode.nodeData, ignoreCache: false })); @@ -1049,7 +1049,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.reloadTree(undefined)?.complete; + await actions.reloadTree(undefined).complete; expect(provider.getNodes).to.be.calledOnce; expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: model.rootNode.nodeData, ignoreCache: false })); @@ -1090,7 +1090,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.reloadTree(undefined)?.complete; + await actions.reloadTree(undefined).complete; expect(provider.getNodes).to.be.calledOnce; expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: model.rootNode.nodeData, ignoreCache: false })); @@ -1127,7 +1127,7 @@ describe("TreeActions", () => { const actions = createActions(model); - await actions.reloadTree({ parentNodeId: "root-1", state: "reset" })?.complete; + await actions.reloadTree({ parentNodeId: "root-1", state: "reset" }).complete; expect(provider.getNodes).to.be.calledOnce; expect(provider.getNodes).to.be.calledWith(createGetNodesProps({ parentNode: getHierarchyNode(model, "root-1")?.nodeData, ignoreCache: true })); @@ -1154,7 +1154,7 @@ describe("TreeActions", () => { return createAsyncIterator([createTestHierarchyNode({ id: "root-1" })]); }); - await actions.reloadTree()?.complete; + await actions.reloadTree().complete; expect(onModelChangedStub).to.be.called; expect(onLoadStub).to.be.calledWith("initial-load"); @@ -1169,7 +1169,7 @@ describe("TreeActions", () => { return throwingAsyncIterator(error); }); - await actions.reloadTree()?.complete; + await actions.reloadTree().complete; expect(onModelChangedStub).to.be.called; expect(onLoadStub).to.be.calledWith("initial-load", Number.MAX_SAFE_INTEGER); diff --git a/packages/hierarchies/src/test/Utils.ts b/packages/hierarchies/src/test/Utils.ts index a2c084b23..12f97aedc 100644 --- a/packages/hierarchies/src/test/Utils.ts +++ b/packages/hierarchies/src/test/Utils.ts @@ -199,18 +199,18 @@ export type TStubEntityClassFunc = (props: StubClassFuncProps) => EC.EntityClass export type TStubRelationshipClassFunc = (props: StubRelationshipClassFuncProps) => EC.RelationshipClass & ECClassExtraMembers; export function createECSchemaProviderStub() { - const schemaStubs: { [schemaName: string]: sinon.SinonStubbedInstance } = {}; + const schemaStubs = new Map>(); const classes = new Dictionary(compareFullClassNames); // className -> class const classHierarchy = new Dictionary(compareFullClassNames); // className -> baseClassName const getSchemaStub = sinon.stub<[string], sinon.SinonStubbedInstance>().callsFake((schemaName: string) => { - let schemaStub = schemaStubs[schemaName]; + let schemaStub = schemaStubs.get(schemaName); if (!schemaStub) { schemaStub = { name: schemaName, getClass: sinon.stub<[string], Promise>().callsFake(async (className) => classes.get(`${schemaName}.${className}`)), getCustomAttributes: sinon.stub<[], Promise>().callsFake(async () => new Map()), }; - schemaStubs[schemaName] = schemaStub; + schemaStubs.set(schemaName, schemaStub); } return schemaStub; }); diff --git a/packages/hierarchies/src/test/imodel/NodeSelectQueryFactory.test.ts b/packages/hierarchies/src/test/imodel/NodeSelectQueryFactory.test.ts index 80b724c5b..b5c24938c 100644 --- a/packages/hierarchies/src/test/imodel/NodeSelectQueryFactory.test.ts +++ b/packages/hierarchies/src/test/imodel/NodeSelectQueryFactory.test.ts @@ -599,7 +599,7 @@ describe("createNodesQueryClauseFactory", () => { rules: rule, }; const res = await factory.createFilterClauses({ filter, contentClass: { fullName: className, alias: classAlias } }); - expect(trimWhitespace(res.where ?? "")).to.eq(trimWhitespace(expectedECSql)); + expect(trimWhitespace(res.where)).to.eq(trimWhitespace(expectedECSql)); } it(`defaults to content class alias if it's not specified for property rule`, async () => { @@ -1102,8 +1102,8 @@ describe("createNodesQueryClauseFactory", () => { const relationship = imodelAccess.stubRelationshipClass({ schemaName: "x", className: "rel", - source: { polymorphic: false, abstractConstraint: Promise.resolve(contentClass) }, - target: { polymorphic: false, abstractConstraint: Promise.resolve(propertyClass) }, + source: { polymorphic: false, abstractConstraint: Promise.resolve(contentClass), multiplicity: { lowerLimit: 0, upperLimit: 1 } }, + target: { polymorphic: false, abstractConstraint: Promise.resolve(propertyClass), multiplicity: { lowerLimit: 0, upperLimit: 1 } }, }); await testPropertyFilter({ className: contentClass.fullName, @@ -1144,8 +1144,8 @@ describe("createNodesQueryClauseFactory", () => { const relationship = imodelAccess.stubRelationshipClass({ schemaName: "x", className: "rel", - source: { polymorphic: false, abstractConstraint: Promise.resolve(contentClass) }, - target: { polymorphic: false, abstractConstraint: Promise.resolve(propertyClass) }, + source: { polymorphic: false, abstractConstraint: Promise.resolve(contentClass), multiplicity: { lowerLimit: 0, upperLimit: 1 } }, + target: { polymorphic: false, abstractConstraint: Promise.resolve(propertyClass), multiplicity: { lowerLimit: 0, upperLimit: 1 } }, }); await expect( factory.createFilterClauses({ @@ -1422,10 +1422,12 @@ describe("createNodesQueryClauseFactory", () => { source: { abstractConstraint: Promise.resolve(sourceClass), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, target: { abstractConstraint: Promise.resolve(imodelAccess.stubEntityClass({ schemaName: "x", className: "t" })), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, }); const filter: GenericInstanceFilter = { @@ -1468,10 +1470,12 @@ describe("createNodesQueryClauseFactory", () => { source: { abstractConstraint: Promise.resolve(sourceClass), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, target: { abstractConstraint: Promise.resolve(intermediateClass), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, }); imodelAccess.stubRelationshipClass({ @@ -1481,10 +1485,12 @@ describe("createNodesQueryClauseFactory", () => { source: { abstractConstraint: Promise.resolve(intermediateClass), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, target: { abstractConstraint: Promise.resolve(targetClass), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, }); const filter: GenericInstanceFilter = { @@ -1535,10 +1541,12 @@ describe("createNodesQueryClauseFactory", () => { source: { abstractConstraint: Promise.resolve(sourceClass), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, target: { abstractConstraint: Promise.resolve(targetClass1), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, }); imodelAccess.stubRelationshipClass({ @@ -1548,10 +1556,12 @@ describe("createNodesQueryClauseFactory", () => { source: { abstractConstraint: Promise.resolve(sourceClass), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, target: { abstractConstraint: Promise.resolve(targetClass2), polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, }, }); const filter: GenericInstanceFilter = { diff --git a/packages/models-tree/src/models-tree/ModelsTreeIdsCache.ts b/packages/models-tree/src/models-tree/ModelsTreeIdsCache.ts index 9ed0a7296..656d808b0 100644 --- a/packages/models-tree/src/models-tree/ModelsTreeIdsCache.ts +++ b/packages/models-tree/src/models-tree/ModelsTreeIdsCache.ts @@ -384,7 +384,7 @@ export class ModelsTreeIdsCache { entry = (async () => { const result = new Set(); const modelInfos = await this.getModelInfos(); - modelInfos?.forEach((modelInfo, modelId) => { + modelInfos.forEach((modelInfo, modelId) => { if (modelInfo.categories.has(categoryId)) { result.add(modelId); } diff --git a/packages/shared/api/presentation-shared.api.md b/packages/shared/api/presentation-shared.api.md index f17a6dfbc..38de32161 100644 --- a/packages/shared/api/presentation-shared.api.md +++ b/packages/shared/api/presentation-shared.api.md @@ -247,7 +247,7 @@ export namespace EC { // (undocumented) abstractConstraint: Promise; // (undocumented) - multiplicity?: RelationshipConstraintMultiplicity; + multiplicity: RelationshipConstraintMultiplicity; // (undocumented) polymorphic: boolean; } diff --git a/packages/shared/src/shared/Metadata.ts b/packages/shared/src/shared/Metadata.ts index 1157dc460..cfe6b4fbc 100644 --- a/packages/shared/src/shared/Metadata.ts +++ b/packages/shared/src/shared/Metadata.ts @@ -150,7 +150,7 @@ export namespace EC { * @public */ export interface RelationshipConstraint { - multiplicity?: RelationshipConstraintMultiplicity; + multiplicity: RelationshipConstraintMultiplicity; polymorphic: boolean; abstractConstraint: Promise; } diff --git a/packages/shared/src/shared/Values.ts b/packages/shared/src/shared/Values.ts index 4d1182723..c92df9755 100644 --- a/packages/shared/src/shared/Values.ts +++ b/packages/shared/src/shared/Values.ts @@ -83,8 +83,7 @@ export namespace PrimitiveValue { if (typeof value !== "object") { return false; } - const pt = value as Point2d; - return pt.x !== undefined && pt.y !== undefined; + return "x" in value && "y" in value; } /** @@ -95,8 +94,7 @@ export namespace PrimitiveValue { if (typeof value !== "object") { return false; } - const pt = value as Point3d; - return pt.x !== undefined && pt.y !== undefined && pt.z !== undefined; + return "x" in value && "y" in value && "z" in value; } } diff --git a/packages/shared/src/test/MetadataProviderStub.ts b/packages/shared/src/test/MetadataProviderStub.ts index 1df384884..492102297 100644 --- a/packages/shared/src/test/MetadataProviderStub.ts +++ b/packages/shared/src/test/MetadataProviderStub.ts @@ -31,21 +31,21 @@ export interface ClassStubs { stub: sinon.SinonStub<[schemaProvider: ECSchemaProvider, fullClassName: string], Promise>; } export function createECSchemaProviderStub() { - const schemaStubs: { [schemaName: string]: sinon.SinonStubbedInstance } = {}; + const schemaStubs = new Map>(); const stub = { getSchema: sinon.fake(async (schemaName: string): Promise => { - return schemaStubs[schemaName]; + return schemaStubs.get(schemaName); }), }; const getSchemaStub = (schemaName: string) => { - let schemaStub = schemaStubs[schemaName]; + let schemaStub = schemaStubs.get(schemaName); if (!schemaStub) { schemaStub = { name: schemaName, getClass: sinon.stub(), getCustomAttributes: sinon.stub(), }; - schemaStubs[schemaName] = schemaStub; + schemaStubs.set(schemaName, schemaStub); } return schemaStub; }; @@ -109,7 +109,7 @@ export function createECSchemaProviderStub() { stubRelationshipClass, stubOtherClass, getClassRequestCount(props: { schemaName: string; className: string }): number { - const schemaStub = schemaStubs[props.schemaName]; + const schemaStub = schemaStubs.get(props.schemaName); if (!schemaStub) { return 0; } diff --git a/packages/shared/src/test/ecsql-snippets/ECSqlJoinSnippets.test.ts b/packages/shared/src/test/ecsql-snippets/ECSqlJoinSnippets.test.ts index 39ca35a03..924d9fb17 100644 --- a/packages/shared/src/test/ecsql-snippets/ECSqlJoinSnippets.test.ts +++ b/packages/shared/src/test/ecsql-snippets/ECSqlJoinSnippets.test.ts @@ -554,7 +554,7 @@ describe("createRelationshipPathJoinClause", () => { }) { const navigationRelationshipRes = new ResolvablePromise(); const navigationProperty = { - name: props?.navigationPropertyName ?? "navigation-property", + name: props.navigationPropertyName ?? "navigation-property", isNavigation: () => true, direction: props.navigationPropertyDirection, relationshipClass: navigationRelationshipRes, @@ -577,10 +577,12 @@ describe("createRelationshipPathJoinClause", () => { direction: "Forward", source: { polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: 1 }, abstractConstraint: Promise.resolve(sourceClass), }, target: { polymorphic: false, + multiplicity: { lowerLimit: 0, upperLimit: INT32_MAX }, abstractConstraint: Promise.resolve(targetClass), }, ...(typeof props.relationship === "object" ? props.relationship : undefined), @@ -614,12 +616,17 @@ describe("createRelationshipPathJoinClause", () => { source: { polymorphic: false, abstractConstraint: Promise.resolve(sourceClass), + multiplicity: { lowerLimit: 0, upperLimit: INT32_MAX }, }, target: { polymorphic: false, abstractConstraint: Promise.resolve(targetClass), + multiplicity: { lowerLimit: 0, upperLimit: INT32_MAX }, }, }); return { sourceClass, targetClass, relationship }; } }); + +// taken from `@itwin/ecschema-metadata` +const INT32_MAX = 2147483647; diff --git a/packages/testing/src/presentation-testing/Helpers.ts b/packages/testing/src/presentation-testing/Helpers.ts index 498350654..7916d6d89 100644 --- a/packages/testing/src/presentation-testing/Helpers.ts +++ b/packages/testing/src/presentation-testing/Helpers.ts @@ -144,10 +144,10 @@ export const terminate = async (frontendApp = IModelApp) => { const hierarchiesCacheConfig = PresentationBackend.initProps?.caching?.hierarchies; // eslint-disable-next-line @typescript-eslint/no-deprecated if (hierarchiesCacheConfig?.mode === HierarchyCacheMode.Disk) { - hierarchiesCacheDirectory = hierarchiesCacheConfig?.directory; + hierarchiesCacheDirectory = hierarchiesCacheConfig.directory; // eslint-disable-next-line @typescript-eslint/no-deprecated } else if (hierarchiesCacheConfig?.mode === HierarchyCacheMode.Hybrid) { - hierarchiesCacheDirectory = hierarchiesCacheConfig?.disk?.directory; + hierarchiesCacheDirectory = hierarchiesCacheConfig.disk?.directory; } // terminate backend diff --git a/packages/testing/src/test/HierarchyBuilder.test.ts b/packages/testing/src/test/HierarchyBuilder.test.ts index 094b2ab75..f7ff0dad1 100644 --- a/packages/testing/src/test/HierarchyBuilder.test.ts +++ b/packages/testing/src/test/HierarchyBuilder.test.ts @@ -25,7 +25,7 @@ async function getRootNodes() { } async function getChildrenNodes(opts: HierarchyRequestOptions) { - if (opts.parentKey?.pathFromRoot[0] !== "root" || opts?.parentKey.pathFromRoot.length !== 1) { + if (opts.parentKey?.pathFromRoot[0] !== "root" || opts.parentKey.pathFromRoot.length !== 1) { return { items: createAsyncIterator([]), total: 0 }; }