mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-20 09:01:19 +00:00
refactor: make fetchContext required on FeedSource
Sources that cannot provide context now return null instead of omitting the method. The engine checks the return value rather than method existence. Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
@@ -74,7 +74,7 @@ function createWeatherSource(
|
|||||||
|
|
||||||
async fetchContext(context) {
|
async fetchContext(context) {
|
||||||
const location = contextValue(context, LocationKey)
|
const location = contextValue(context, LocationKey)
|
||||||
if (!location) return {}
|
if (!location) return null
|
||||||
|
|
||||||
const weather = await fetchWeather(location)
|
const weather = await fetchWeather(location)
|
||||||
return { [WeatherKey]: weather }
|
return { [WeatherKey]: weather }
|
||||||
@@ -105,6 +105,10 @@ function createAlertSource(): FeedSource<AlertFeedItem> {
|
|||||||
id: "alert",
|
id: "alert",
|
||||||
dependencies: ["weather"],
|
dependencies: ["weather"],
|
||||||
|
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
async fetchItems(context) {
|
async fetchItems(context) {
|
||||||
const weather = contextValue(context, WeatherKey)
|
const weather = contextValue(context, WeatherKey)
|
||||||
if (!weather) return []
|
if (!weather) return []
|
||||||
@@ -169,6 +173,9 @@ describe("FeedEngine", () => {
|
|||||||
const orphan: FeedSource = {
|
const orphan: FeedSource = {
|
||||||
id: "orphan",
|
id: "orphan",
|
||||||
dependencies: ["nonexistent"],
|
dependencies: ["nonexistent"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.register(orphan)
|
engine.register(orphan)
|
||||||
@@ -180,8 +187,20 @@ describe("FeedEngine", () => {
|
|||||||
|
|
||||||
test("throws on circular dependency", () => {
|
test("throws on circular dependency", () => {
|
||||||
const engine = new FeedEngine()
|
const engine = new FeedEngine()
|
||||||
const a: FeedSource = { id: "a", dependencies: ["b"] }
|
const a: FeedSource = {
|
||||||
const b: FeedSource = { id: "b", dependencies: ["a"] }
|
id: "a",
|
||||||
|
dependencies: ["b"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const b: FeedSource = {
|
||||||
|
id: "b",
|
||||||
|
dependencies: ["a"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
engine.register(a).register(b)
|
engine.register(a).register(b)
|
||||||
|
|
||||||
@@ -190,9 +209,27 @@ describe("FeedEngine", () => {
|
|||||||
|
|
||||||
test("throws on longer cycles", () => {
|
test("throws on longer cycles", () => {
|
||||||
const engine = new FeedEngine()
|
const engine = new FeedEngine()
|
||||||
const a: FeedSource = { id: "a", dependencies: ["c"] }
|
const a: FeedSource = {
|
||||||
const b: FeedSource = { id: "b", dependencies: ["a"] }
|
id: "a",
|
||||||
const c: FeedSource = { id: "c", dependencies: ["b"] }
|
dependencies: ["c"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const b: FeedSource = {
|
||||||
|
id: "b",
|
||||||
|
dependencies: ["a"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const c: FeedSource = {
|
||||||
|
id: "c",
|
||||||
|
dependencies: ["b"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
engine.register(a).register(b).register(c)
|
engine.register(a).register(b).register(c)
|
||||||
|
|
||||||
@@ -282,7 +319,7 @@ describe("FeedEngine", () => {
|
|||||||
const location: FeedSource = {
|
const location: FeedSource = {
|
||||||
id: "location",
|
id: "location",
|
||||||
async fetchContext() {
|
async fetchContext() {
|
||||||
return {} // No location available
|
return null // No location available
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +353,9 @@ describe("FeedEngine", () => {
|
|||||||
test("captures errors from fetchItems", async () => {
|
test("captures errors from fetchItems", async () => {
|
||||||
const failing: FeedSource = {
|
const failing: FeedSource = {
|
||||||
id: "failing",
|
id: "failing",
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
async fetchItems() {
|
async fetchItems() {
|
||||||
throw new Error("Items fetch failed")
|
throw new Error("Items fetch failed")
|
||||||
},
|
},
|
||||||
@@ -340,6 +380,9 @@ describe("FeedEngine", () => {
|
|||||||
|
|
||||||
const working: FeedSource = {
|
const working: FeedSource = {
|
||||||
id: "working",
|
id: "working",
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
async fetchItems() {
|
async fetchItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -89,10 +89,11 @@ export class FeedEngine<TItems extends FeedItem = FeedItem> {
|
|||||||
|
|
||||||
// Run fetchContext in topological order
|
// Run fetchContext in topological order
|
||||||
for (const source of graph.sorted) {
|
for (const source of graph.sorted) {
|
||||||
if (source.fetchContext) {
|
|
||||||
try {
|
try {
|
||||||
const update = await source.fetchContext(context)
|
const update = await source.fetchContext(context)
|
||||||
|
if (update) {
|
||||||
context = { ...context, ...update }
|
context = { ...context, ...update }
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors.push({
|
errors.push({
|
||||||
sourceId: source.id,
|
sourceId: source.id,
|
||||||
@@ -100,7 +101,6 @@ export class FeedEngine<TItems extends FeedItem = FeedItem> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Run fetchItems on all sources
|
// Run fetchItems on all sources
|
||||||
const items: FeedItem[] = []
|
const items: FeedItem[] = []
|
||||||
@@ -208,10 +208,12 @@ export class FeedEngine<TItems extends FeedItem = FeedItem> {
|
|||||||
// Re-run fetchContext for dependents in order
|
// Re-run fetchContext for dependents in order
|
||||||
for (const id of toRefresh) {
|
for (const id of toRefresh) {
|
||||||
const source = graph.sources.get(id)
|
const source = graph.sources.get(id)
|
||||||
if (source?.fetchContext) {
|
if (source) {
|
||||||
try {
|
try {
|
||||||
const update = await source.fetchContext(this.context)
|
const update = await source.fetchContext(this.context)
|
||||||
|
if (update) {
|
||||||
this.context = { ...this.context, ...update }
|
this.context = { ...this.context, ...update }
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Errors during reactive updates are logged but don't stop propagation
|
// Errors during reactive updates are logged but don't stop propagation
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ function createWeatherSource(
|
|||||||
|
|
||||||
async fetchContext(context) {
|
async fetchContext(context) {
|
||||||
const location = contextValue(context, LocationKey)
|
const location = contextValue(context, LocationKey)
|
||||||
if (!location) return {}
|
if (!location) return null
|
||||||
|
|
||||||
const weather = await fetchWeather(location)
|
const weather = await fetchWeather(location)
|
||||||
return { [WeatherKey]: weather }
|
return { [WeatherKey]: weather }
|
||||||
@@ -104,6 +104,10 @@ function createAlertSource(): FeedSource<AlertFeedItem> {
|
|||||||
id: "alert",
|
id: "alert",
|
||||||
dependencies: ["weather"],
|
dependencies: ["weather"],
|
||||||
|
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
async fetchItems(context) {
|
async fetchItems(context) {
|
||||||
const weather = contextValue(context, WeatherKey)
|
const weather = contextValue(context, WeatherKey)
|
||||||
if (!weather) return []
|
if (!weather) return []
|
||||||
@@ -194,8 +198,8 @@ async function refreshGraph(graph: SourceGraph): Promise<{ context: Context; ite
|
|||||||
|
|
||||||
// Run fetchContext in topological order
|
// Run fetchContext in topological order
|
||||||
for (const source of graph.sorted) {
|
for (const source of graph.sorted) {
|
||||||
if (source.fetchContext) {
|
|
||||||
const update = await source.fetchContext(context)
|
const update = await source.fetchContext(context)
|
||||||
|
if (update) {
|
||||||
context = { ...context, ...update }
|
context = { ...context, ...update }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,9 +249,15 @@ describe("FeedSource", () => {
|
|||||||
|
|
||||||
expect(source.id).toBe("alert")
|
expect(source.id).toBe("alert")
|
||||||
expect(source.dependencies).toEqual(["weather"])
|
expect(source.dependencies).toEqual(["weather"])
|
||||||
expect(source.fetchContext).toBeUndefined()
|
expect(source.fetchContext).toBeDefined()
|
||||||
expect(source.fetchItems).toBeDefined()
|
expect(source.fetchItems).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("source without context returns null from fetchContext", async () => {
|
||||||
|
const source = createAlertSource()
|
||||||
|
const result = await source.fetchContext({ time: new Date() })
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("graph validation", () => {
|
describe("graph validation", () => {
|
||||||
@@ -255,6 +265,9 @@ describe("FeedSource", () => {
|
|||||||
const orphan: FeedSource = {
|
const orphan: FeedSource = {
|
||||||
id: "orphan",
|
id: "orphan",
|
||||||
dependencies: ["nonexistent"],
|
dependencies: ["nonexistent"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(() => buildGraph([orphan])).toThrow(
|
expect(() => buildGraph([orphan])).toThrow(
|
||||||
@@ -263,16 +276,46 @@ describe("FeedSource", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("detects circular dependencies", () => {
|
test("detects circular dependencies", () => {
|
||||||
const a: FeedSource = { id: "a", dependencies: ["b"] }
|
const a: FeedSource = {
|
||||||
const b: FeedSource = { id: "b", dependencies: ["a"] }
|
id: "a",
|
||||||
|
dependencies: ["b"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const b: FeedSource = {
|
||||||
|
id: "b",
|
||||||
|
dependencies: ["a"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
expect(() => buildGraph([a, b])).toThrow("Circular dependency detected: a → b → a")
|
expect(() => buildGraph([a, b])).toThrow("Circular dependency detected: a → b → a")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("detects longer cycles", () => {
|
test("detects longer cycles", () => {
|
||||||
const a: FeedSource = { id: "a", dependencies: ["c"] }
|
const a: FeedSource = {
|
||||||
const b: FeedSource = { id: "b", dependencies: ["a"] }
|
id: "a",
|
||||||
const c: FeedSource = { id: "c", dependencies: ["b"] }
|
dependencies: ["c"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const b: FeedSource = {
|
||||||
|
id: "b",
|
||||||
|
dependencies: ["a"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const c: FeedSource = {
|
||||||
|
id: "c",
|
||||||
|
dependencies: ["b"],
|
||||||
|
async fetchContext() {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
expect(() => buildGraph([a, b, c])).toThrow("Circular dependency detected")
|
expect(() => buildGraph([a, b, c])).toThrow("Circular dependency detected")
|
||||||
})
|
})
|
||||||
@@ -376,12 +419,12 @@ describe("FeedSource", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("source without location context returns empty items", async () => {
|
test("source without location context returns empty items", async () => {
|
||||||
// Location source exists but hasn't been updated (returns default 0,0)
|
// Location source exists but hasn't been updated
|
||||||
const location: FeedSource = {
|
const location: FeedSource = {
|
||||||
id: "location",
|
id: "location",
|
||||||
async fetchContext() {
|
async fetchContext() {
|
||||||
// Simulate no location available
|
// Simulate no location available
|
||||||
return {}
|
return null
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ import type { FeedItem } from "./feed"
|
|||||||
* return createWeatherFeedItems(ctx.weather)
|
* return createWeatherFeedItems(ctx.weather)
|
||||||
* },
|
* },
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
|
* // TFL source - no context to provide
|
||||||
|
* const tflSource: FeedSource<TflFeedItem> = {
|
||||||
|
* id: "tfl",
|
||||||
|
* fetchContext: async () => null,
|
||||||
|
* fetchItems: async (ctx) => { ... },
|
||||||
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export interface FeedSource<TItem extends FeedItem = FeedItem> {
|
export interface FeedSource<TItem extends FeedItem = FeedItem> {
|
||||||
@@ -58,8 +65,9 @@ export interface FeedSource<TItem extends FeedItem = FeedItem> {
|
|||||||
/**
|
/**
|
||||||
* Fetch context on-demand.
|
* Fetch context on-demand.
|
||||||
* Called during manual refresh or initial load.
|
* Called during manual refresh or initial load.
|
||||||
|
* Return null if this source cannot provide context.
|
||||||
*/
|
*/
|
||||||
fetchContext?(context: Context): Promise<Partial<Context>>
|
fetchContext(context: Context): Promise<Partial<Context> | null>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to reactive feed item updates.
|
* Subscribe to reactive feed item updates.
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class CalendarSource implements FeedSource<CalendarFeedItem> {
|
|||||||
this.injectedClient = options?.davClient ?? null
|
this.injectedClient = options?.davClient ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchContext(context: Context): Promise<Partial<Context>> {
|
async fetchContext(context: Context): Promise<Partial<Context> | null> {
|
||||||
const events = await this.fetchEvents(context)
|
const events = await this.fetchEvents(context)
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -197,13 +197,13 @@ describe("GoogleCalendarSource", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("fetchContext", () => {
|
describe("fetchContext", () => {
|
||||||
test("returns empty when no events", async () => {
|
test("returns null when no events", async () => {
|
||||||
const source = new GoogleCalendarSource({ client: createMockClient({ primary: [] }) })
|
const source = new GoogleCalendarSource({ client: createMockClient({ primary: [] }) })
|
||||||
const result = await source.fetchContext(createContext())
|
const result = await source.fetchContext(createContext())
|
||||||
expect(result).toEqual({})
|
expect(result).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns empty when only all-day events", async () => {
|
test("returns null when only all-day events", async () => {
|
||||||
const allDayOnly: ApiCalendarEvent[] = [
|
const allDayOnly: ApiCalendarEvent[] = [
|
||||||
{
|
{
|
||||||
id: "evt-allday",
|
id: "evt-allday",
|
||||||
@@ -218,14 +218,15 @@ describe("GoogleCalendarSource", () => {
|
|||||||
client: createMockClient({ primary: allDayOnly }),
|
client: createMockClient({ primary: allDayOnly }),
|
||||||
})
|
})
|
||||||
const result = await source.fetchContext(createContext())
|
const result = await source.fetchContext(createContext())
|
||||||
expect(result).toEqual({})
|
expect(result).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns next upcoming timed event (not ongoing)", async () => {
|
test("returns next upcoming timed event (not ongoing)", async () => {
|
||||||
const source = new GoogleCalendarSource({ client: defaultMockClient() })
|
const source = new GoogleCalendarSource({ client: defaultMockClient() })
|
||||||
const result = await source.fetchContext(createContext())
|
const result = await source.fetchContext(createContext())
|
||||||
|
|
||||||
const nextEvent = contextValue(result as Context, NextEventKey)
|
expect(result).not.toBeNull()
|
||||||
|
const nextEvent = contextValue(result! as Context, NextEventKey)
|
||||||
expect(nextEvent).toBeDefined()
|
expect(nextEvent).toBeDefined()
|
||||||
// evt-soon starts at 10:10, which is the nearest future timed event
|
// evt-soon starts at 10:10, which is the nearest future timed event
|
||||||
expect(nextEvent!.title).toBe("1:1 with Manager")
|
expect(nextEvent!.title).toBe("1:1 with Manager")
|
||||||
@@ -250,7 +251,8 @@ describe("GoogleCalendarSource", () => {
|
|||||||
})
|
})
|
||||||
const result = await source.fetchContext(createContext())
|
const result = await source.fetchContext(createContext())
|
||||||
|
|
||||||
const nextEvent = contextValue(result as Context, NextEventKey)
|
expect(result).not.toBeNull()
|
||||||
|
const nextEvent = contextValue(result! as Context, NextEventKey)
|
||||||
expect(nextEvent).toBeDefined()
|
expect(nextEvent).toBeDefined()
|
||||||
expect(nextEvent!.location).toBe("123 Main St")
|
expect(nextEvent!.location).toBe("123 Main St")
|
||||||
})
|
})
|
||||||
@@ -270,7 +272,7 @@ describe("GoogleCalendarSource", () => {
|
|||||||
client: createMockClient({ primary: events }),
|
client: createMockClient({ primary: events }),
|
||||||
})
|
})
|
||||||
const result = await source.fetchContext(createContext())
|
const result = await source.fetchContext(createContext())
|
||||||
expect(result).toEqual({})
|
expect(result).toBeNull()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ export class GoogleCalendarSource implements FeedSource<CalendarFeedItem> {
|
|||||||
this.lookaheadHours = options.lookaheadHours ?? DEFAULT_LOOKAHEAD_HOURS
|
this.lookaheadHours = options.lookaheadHours ?? DEFAULT_LOOKAHEAD_HOURS
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchContext(context: Context): Promise<Partial<Context>> {
|
async fetchContext(context: Context): Promise<Partial<Context> | null> {
|
||||||
const events = await this.fetchAllEvents(context.time)
|
const events = await this.fetchAllEvents(context.time)
|
||||||
|
|
||||||
const now = context.time.getTime()
|
const now = context.time.getTime()
|
||||||
const nextTimedEvent = events.find((e) => !e.isAllDay && e.startTime.getTime() > now)
|
const nextTimedEvent = events.find((e) => !e.isAllDay && e.startTime.getTime() > now)
|
||||||
|
|
||||||
if (!nextTimedEvent) {
|
if (!nextTimedEvent) {
|
||||||
return {}
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const minutesUntilStart = (nextTimedEvent.startTime.getTime() - now) / 60_000
|
const minutesUntilStart = (nextTimedEvent.startTime.getTime() - now) / 60_000
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ describe("LocationSource", () => {
|
|||||||
expect(items).toEqual([])
|
expect(items).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("fetchContext returns empty when no location", async () => {
|
test("fetchContext returns null when no location", async () => {
|
||||||
const source = new LocationSource()
|
const source = new LocationSource()
|
||||||
|
|
||||||
const context = await source.fetchContext()
|
const context = await source.fetchContext()
|
||||||
expect(context).toEqual({})
|
expect(context).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("fetchContext returns location when available", async () => {
|
test("fetchContext returns location when available", async () => {
|
||||||
|
|||||||
@@ -73,11 +73,11 @@ export class LocationSource implements FeedSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchContext(): Promise<Partial<Context>> {
|
async fetchContext(): Promise<Partial<Context> | null> {
|
||||||
if (this.lastLocation) {
|
if (this.lastLocation) {
|
||||||
return { [LocationKey]: this.lastLocation }
|
return { [LocationKey]: this.lastLocation }
|
||||||
}
|
}
|
||||||
return {}
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchItems(): Promise<[]> {
|
async fetchItems(): Promise<[]> {
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ export class TflSource implements FeedSource<TflAlertFeedItem> {
|
|||||||
this.lines = options.lines ?? [...TflSource.DEFAULT_LINES_OF_INTEREST]
|
this.lines = options.lines ?? [...TflSource.DEFAULT_LINES_OF_INTEREST]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchContext(): Promise<null> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the set of monitored lines. Takes effect on the next fetchItems call.
|
* Update the set of monitored lines. Takes effect on the next fetchItems call.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ describe("WeatherSource", () => {
|
|||||||
describe("fetchContext", () => {
|
describe("fetchContext", () => {
|
||||||
const mockClient = createMockClient(fixture.response as WeatherKitResponse)
|
const mockClient = createMockClient(fixture.response as WeatherKitResponse)
|
||||||
|
|
||||||
test("returns empty when no location", async () => {
|
test("returns null when no location", async () => {
|
||||||
const source = new WeatherSource({ client: mockClient })
|
const source = new WeatherSource({ client: mockClient })
|
||||||
const result = await source.fetchContext(createMockContext())
|
const result = await source.fetchContext(createMockContext())
|
||||||
|
|
||||||
expect(result).toEqual({})
|
expect(result).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns simplified weather context", async () => {
|
test("returns simplified weather context", async () => {
|
||||||
@@ -64,7 +64,8 @@ describe("WeatherSource", () => {
|
|||||||
const context = createMockContext({ lat: 37.7749, lng: -122.4194 })
|
const context = createMockContext({ lat: 37.7749, lng: -122.4194 })
|
||||||
|
|
||||||
const result = await source.fetchContext(context)
|
const result = await source.fetchContext(context)
|
||||||
const weather = contextValue(result, WeatherKey)
|
expect(result).not.toBeNull()
|
||||||
|
const weather = contextValue(result! as Context, WeatherKey)
|
||||||
|
|
||||||
expect(weather).toBeDefined()
|
expect(weather).toBeDefined()
|
||||||
expect(typeof weather!.temperature).toBe("number")
|
expect(typeof weather!.temperature).toBe("number")
|
||||||
@@ -81,7 +82,8 @@ describe("WeatherSource", () => {
|
|||||||
const context = createMockContext({ lat: 37.7749, lng: -122.4194 })
|
const context = createMockContext({ lat: 37.7749, lng: -122.4194 })
|
||||||
|
|
||||||
const result = await source.fetchContext(context)
|
const result = await source.fetchContext(context)
|
||||||
const weather = contextValue(result, WeatherKey)
|
expect(result).not.toBeNull()
|
||||||
|
const weather = contextValue(result! as Context, WeatherKey)
|
||||||
|
|
||||||
// Fixture has temperature around 10°C, imperial should be around 50°F
|
// Fixture has temperature around 10°C, imperial should be around 50°F
|
||||||
expect(weather!.temperature).toBeGreaterThan(40)
|
expect(weather!.temperature).toBeGreaterThan(40)
|
||||||
|
|||||||
@@ -111,10 +111,10 @@ export class WeatherSource implements FeedSource<WeatherFeedItem> {
|
|||||||
this.units = options.units ?? Units.metric
|
this.units = options.units ?? Units.metric
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchContext(context: Context): Promise<Partial<Context>> {
|
async fetchContext(context: Context): Promise<Partial<Context> | null> {
|
||||||
const location = contextValue(context, LocationKey)
|
const location = contextValue(context, LocationKey)
|
||||||
if (!location) {
|
if (!location) {
|
||||||
return {}
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.client.fetch({
|
const response = await this.client.fetch({
|
||||||
@@ -123,7 +123,7 @@ export class WeatherSource implements FeedSource<WeatherFeedItem> {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!response.currentWeather) {
|
if (!response.currentWeather) {
|
||||||
return {}
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const weather: Weather = {
|
const weather: Weather = {
|
||||||
|
|||||||
Reference in New Issue
Block a user