Skip to content

Commit 319d257

Browse files
committed
fix: #631 c-loader-status no-initial-content flag now behaves more consistently.
1 parent 0592e40 commit 319d257

File tree

5 files changed

+76
-6
lines changed

5 files changed

+76
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 6.1.0
22
- Added support for .NET 10
3+
- `c-loader-status`: The `initial-content` flag now behaves more consistently. A loader is now considered to have initial content if it has ever successfully invoked. Previously, some types of failures (e.g. network errors) would not clear this state, but other kinds of failures (explicit server error responses) did clear this state. If errors should result in hidden content, the `no-error-content` flag should be used.
34

45
# 6.0.4
56
- AuditLogging: Stored procedure cache was not being correctly persisted to avoid redundant `SELECT OBJECT_DEFINITION` queries.

docs/stacks/vue/coalesce-vue-vuetify/components/c-loader-status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ The available flags are as follows, all of which default to `true` except for `s
175175
| - | - |
176176
| `no-loading-content` | Controls whether the default slot is rendered while any API caller is loading (i.e. when `caller.isLoading === true`). |
177177
| `no-error-content` | Controls whether the default slot is rendered while any API Caller is in an error state (i.e. when `caller.wasSuccessful === false`). |
178-
| `no-initial-content` | Controls whether the default slot is rendered while any API Caller has yet to receive a response for the first time (i.e. when `caller.wasSuccessful === null`). |
178+
| `no-initial-content` | Controls whether the default slot is rendered while any API Caller has yet to receive a successful response for the first time. |
179179
| `no-progress` | Master toggle for whether the progress indicator is shown in any scenario. |
180180
| `no-initial-progress` | Controls whether the progress indicator is shown when an API Caller is loading for the very first time (i.e. when `caller.wasSuccessful === null`). |
181181
| `no-secondary-progress` | Controls whether the progress indicator is shown when an API Caller is loading any time after its first invocation (i.e. when `caller.wasSuccessful !== null`). |

src/coalesce-vue-vuetify3/src/components/display/c-loader-status.spec.tsx

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mountApp } from "@test/util";
1+
import { mountApp, mockEndpoint, flushPromises } from "@test/util";
22
import CLS from "./c-loader-status.vue";
33
import { ComplexModelViewModel } from "@test-targets/viewmodels.g";
44

@@ -144,4 +144,62 @@ describe("CLoaderStatus", () => {
144144
// Verify the default "Success" message is displayed
145145
expect(wrapper.text()).toContain("Success");
146146
});
147+
148+
test("void-returning endpoint with no-initial-content shows content after success then failure", async () => {
149+
// This tests the fix for void-returning endpoints where hasResult should be true
150+
// when the endpoint succeeds, even though result is null.
151+
const vm = new ComplexModelViewModel();
152+
vm.$primaryKey = 1;
153+
const voidCaller = vm.methodWithOptionalEnumParam;
154+
155+
const mockSuccess = mockEndpoint(
156+
vm.$metadata.methods.methodWithOptionalEnumParam,
157+
() => ({
158+
wasSuccessful: true,
159+
// Void endpoints don't have an 'object' property in the response
160+
}),
161+
);
162+
163+
const wrapper = mountApp(() => (
164+
<CLS loaders={voidCaller} no-initial-content>
165+
<div>Content</div>
166+
</CLS>
167+
));
168+
169+
// Initially, content should be hidden (wasSuccessful is null)
170+
expect(wrapper.text()).not.toContain("Content");
171+
172+
// Call the void endpoint successfully
173+
await voidCaller();
174+
await flushPromises();
175+
176+
// After success, content should be visible
177+
expect(voidCaller.wasSuccessful).toBe(true);
178+
expect(voidCaller.result).toBeNull();
179+
expect(voidCaller._hasLoaded.value).toBe(true);
180+
expect(wrapper.text()).toContain("Content");
181+
182+
mockSuccess.destroy();
183+
184+
// Now mock a failure
185+
const mockFailure = mockEndpoint(
186+
vm.$metadata.methods.methodWithOptionalEnumParam,
187+
() => ({
188+
wasSuccessful: false,
189+
message: "Error occurred",
190+
}),
191+
);
192+
193+
// Call the endpoint again, this time it fails
194+
await expect(voidCaller()).rejects.toThrow();
195+
await flushPromises();
196+
197+
// Content should still be visible because hasLoaded is true
198+
// (the endpoint has successfully loaded before, even though this call failed)
199+
expect(voidCaller.wasSuccessful).toBe(false);
200+
expect(voidCaller._hasLoaded.value).toBe(true);
201+
expect(wrapper.text()).toContain("Content");
202+
203+
mockFailure.destroy();
204+
});
147205
});

src/coalesce-vue-vuetify3/src/components/display/c-loader-status.vue

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,7 @@ const showContent = computed(() => {
341341
// initial content is off, and either:
342342
!flags["initial-content"] &&
343343
// loader has not yet loaded
344-
(loader.wasSuccessful == null ||
345-
// or loader has loaded, but it errored and there's no current result
346-
// (implying it has never successfully loaded).
347-
(loader.wasSuccessful === false && !loader.hasResult))
344+
!loader._hasLoaded.value
348345
) {
349346
return false;
350347
}

src/coalesce-vue/src/api-client.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,12 +1394,26 @@ export abstract class ApiState<
13941394
this.__isLoading.value = v;
13951395
}
13961396

1397+
/**
1398+
* @internal
1399+
* Whether the endpoint has ever successfully loaded.
1400+
* This is internal state because we don't want to confuse users into using it incorrectly
1401+
* (it's somewhat similar to wasSuccessful and hasResult)
1402+
* Could expose if there's a compelling argument for doing so.
1403+
*/
1404+
_hasLoaded = ref(false);
1405+
13971406
private readonly __wasSuccessful = ref<boolean | null>(null);
13981407
/** True if the previous request was successful. */
13991408
get wasSuccessful() {
14001409
return this.__wasSuccessful.value;
14011410
}
14021411
set wasSuccessful(v) {
1412+
if (v === null) {
1413+
this._hasLoaded.value = false;
1414+
} else if (v) {
1415+
this._hasLoaded.value = true;
1416+
}
14031417
this.__wasSuccessful.value = v;
14041418
}
14051419

0 commit comments

Comments
 (0)