mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
293 lines
9.5 KiB
TypeScript
293 lines
9.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2017 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { TimerId } from '../../../src/util/async_queue';
|
|
import { doc, query } from '../../util/helpers';
|
|
|
|
import { describeSpec, specTest } from './describe_spec';
|
|
import { client, spec } from './spec_builder';
|
|
|
|
describeSpec('Persistence:', [], () => {
|
|
specTest(
|
|
'Local mutations are persisted and re-sent',
|
|
['durable-persistence'],
|
|
() => {
|
|
return spec()
|
|
.userSets('collection/key1', { foo: 'bar' })
|
|
.userSets('collection/key2', { baz: 'quu' })
|
|
.restart()
|
|
.expectNumOutstandingWrites(2)
|
|
.writeAcks('collection/key1', 1, { expectUserCallback: false })
|
|
.writeAcks('collection/key2', 2, { expectUserCallback: false })
|
|
.expectNumOutstandingWrites(0);
|
|
}
|
|
);
|
|
|
|
specTest(
|
|
'Persisted local mutations are visible to listeners',
|
|
['durable-persistence'],
|
|
() => {
|
|
const query1 = query('collection');
|
|
return (
|
|
spec()
|
|
.userSets('collection/key1', { foo: 'bar' })
|
|
.userSets('collection/key2', { baz: 'quu' })
|
|
.restart()
|
|
// It should be visible to listens.
|
|
.userListens(query1)
|
|
.expectEvents(query1, {
|
|
added: [
|
|
doc('collection/key1', 0, { foo: 'bar' }).setHasLocalMutations(),
|
|
doc('collection/key2', 0, { baz: 'quu' }).setHasLocalMutations()
|
|
],
|
|
fromCache: true,
|
|
hasPendingWrites: true
|
|
})
|
|
);
|
|
}
|
|
);
|
|
|
|
specTest('Remote documents are persisted', ['durable-persistence'], () => {
|
|
const query1 = query('collection');
|
|
const doc1 = doc('collection/key', 1000, { foo: 'bar' });
|
|
return spec()
|
|
.userListens(query1)
|
|
.watchAcksFull(query1, 1000, doc1)
|
|
.expectEvents(query1, { added: [doc1] })
|
|
.restart()
|
|
.userListens(query1, { resumeToken: 'resume-token-1000' })
|
|
.expectEvents(query1, { added: [doc1], fromCache: true });
|
|
});
|
|
|
|
specTest("Remote documents from watch are not GC'd", [], () => {
|
|
const query1 = query('collection');
|
|
const doc1 = doc('collection/key', 1000, { foo: 'bar' });
|
|
return (
|
|
spec()
|
|
.ensureManualLruGC()
|
|
.userListens(query1)
|
|
.watchAcksFull(query1, 1000, doc1)
|
|
.expectEvents(query1, { added: [doc1] })
|
|
// Normally this would clear the cached remote documents.
|
|
.userUnlistens(query1)
|
|
.userListens(query1, { resumeToken: 'resume-token-1000' })
|
|
.expectEvents(query1, { added: [doc1], fromCache: true })
|
|
);
|
|
});
|
|
|
|
specTest("Remote documents from user sets are not GC'd", [], () => {
|
|
const query1 = query('collection');
|
|
return (
|
|
spec()
|
|
.ensureManualLruGC()
|
|
.userSets('collection/key', { foo: 'bar' })
|
|
// Normally the write would get GC'd from remote documents here.
|
|
.writeAcks('collection/key', 1000)
|
|
.userListens(query1)
|
|
// Version is 0 since we never received a server version via watch.
|
|
.expectEvents(query1, {
|
|
added: [
|
|
doc('collection/key', 1000, {
|
|
foo: 'bar'
|
|
}).setHasCommittedMutations()
|
|
],
|
|
fromCache: true
|
|
})
|
|
);
|
|
});
|
|
|
|
specTest('Mutation Queue is persisted across uid switches', [], () => {
|
|
return spec()
|
|
.userSets('users/anon', { uid: 'anon' })
|
|
.changeUser('user1')
|
|
.expectNumOutstandingWrites(0)
|
|
.userSets('users/user1', { uid: 'user1' })
|
|
.userSets('users/user1', { uid: 'user1', extra: true })
|
|
.changeUser(null)
|
|
.expectNumOutstandingWrites(1)
|
|
.writeAcks('users/anon', 1000)
|
|
.changeUser('user1')
|
|
.expectNumOutstandingWrites(2)
|
|
.writeAcks('users/user1', 2000)
|
|
.writeAcks('users/user1', 3000);
|
|
});
|
|
|
|
specTest(
|
|
'Mutation Queue is persisted across uid switches (with restarts)',
|
|
['durable-persistence'],
|
|
() => {
|
|
return spec()
|
|
.userSets('users/anon', { uid: 'anon' })
|
|
.changeUser('user1')
|
|
.expectNumOutstandingWrites(0)
|
|
.userSets('users/user1', { uid: 'user1' })
|
|
.userSets('users/user1', { uid: 'user1', extra: true })
|
|
.changeUser(null)
|
|
.restart()
|
|
.expectNumOutstandingWrites(1)
|
|
.writeAcks('users/anon', 1000, { expectUserCallback: false })
|
|
.changeUser('user1')
|
|
.restart()
|
|
.expectNumOutstandingWrites(2)
|
|
.writeAcks('users/user1', 2000, { expectUserCallback: false })
|
|
.writeAcks('users/user2', 3000, { expectUserCallback: false });
|
|
}
|
|
);
|
|
|
|
specTest('Visible mutations reflect uid switches', [], () => {
|
|
const query1 = query('users');
|
|
const existingDoc = doc('users/existing', 0, { uid: 'existing' });
|
|
const anonDoc = doc('users/anon', 0, {
|
|
uid: 'anon'
|
|
}).setHasLocalMutations();
|
|
const user1Doc = doc('users/user1', 0, {
|
|
uid: 'user1'
|
|
}).setHasLocalMutations();
|
|
return (
|
|
spec()
|
|
.userListens(query1)
|
|
.watchAcksFull(query1, 500, existingDoc)
|
|
.expectEvents(query1, { added: [existingDoc] })
|
|
.userSets('users/anon', { uid: 'anon' })
|
|
.expectEvents(query1, { added: [anonDoc], hasPendingWrites: true })
|
|
.changeUser('user1')
|
|
// A user change will re-send the query with the current resume token
|
|
.expectActiveTargets({ query: query1, resumeToken: 'resume-token-500' })
|
|
.expectEvents(query1, { removed: [anonDoc] })
|
|
.userSets('users/user1', { uid: 'user1' })
|
|
.expectEvents(query1, { added: [user1Doc], hasPendingWrites: true })
|
|
.changeUser(null)
|
|
.expectEvents(query1, {
|
|
removed: [user1Doc],
|
|
added: [anonDoc],
|
|
hasPendingWrites: true
|
|
})
|
|
);
|
|
});
|
|
|
|
specTest('Detects all active clients', ['multi-client'], () => {
|
|
return (
|
|
client(0)
|
|
// While we don't verify the client's visibility in this test, the spec
|
|
// test framework requires an explicit action before setting an
|
|
// expectation.
|
|
.becomeHidden()
|
|
.expectNumActiveClients(1)
|
|
.client(1)
|
|
.becomeVisible()
|
|
.expectNumActiveClients(2)
|
|
);
|
|
});
|
|
|
|
specTest('Single tab acquires primary lease', ['multi-client'], () => {
|
|
// This test simulates primary state handoff between two background tabs.
|
|
// With all instances in the background, the first active tab acquires
|
|
// ownership.
|
|
return client(0)
|
|
.becomeHidden()
|
|
.expectPrimaryState(true)
|
|
.client(1)
|
|
.becomeHidden()
|
|
.expectPrimaryState(false)
|
|
.client(0)
|
|
.shutdown()
|
|
.client(1)
|
|
.runTimer(TimerId.ClientMetadataRefresh)
|
|
.expectPrimaryState(true);
|
|
});
|
|
|
|
specTest('Foreground tab acquires primary lease', ['multi-client'], () => {
|
|
// This test verifies that in a multi-client scenario, a foreground tab
|
|
// takes precedence when a new primary client is elected.
|
|
return (
|
|
client(0)
|
|
.becomeHidden()
|
|
.expectPrimaryState(true)
|
|
.client(1)
|
|
.becomeHidden()
|
|
.expectPrimaryState(false)
|
|
.client(2)
|
|
.becomeVisible()
|
|
.expectPrimaryState(false)
|
|
.client(0)
|
|
// Shutdown the client that is currently holding the primary lease.
|
|
.shutdown()
|
|
.client(1)
|
|
// Client 1 is in the background and doesn't grab the primary lease as
|
|
// client 2 is in the foreground.
|
|
.runTimer(TimerId.ClientMetadataRefresh)
|
|
.expectPrimaryState(false)
|
|
.client(2)
|
|
.runTimer(TimerId.ClientMetadataRefresh)
|
|
.expectPrimaryState(true)
|
|
);
|
|
});
|
|
|
|
specTest('Primary lease bound to network state', ['multi-client'], () => {
|
|
return (
|
|
client(0)
|
|
// If there is only a single tab, the online state is ignored and the
|
|
// tab is always primary
|
|
.expectPrimaryState(true)
|
|
.disableNetwork()
|
|
.expectPrimaryState(true)
|
|
.client(1)
|
|
.expectPrimaryState(false)
|
|
.client(0)
|
|
// If the primary tab is offline, and another tab becomes active, the
|
|
// primary tab releases its primary lease.
|
|
.runTimer(TimerId.ClientMetadataRefresh)
|
|
.expectPrimaryState(false)
|
|
.client(1)
|
|
.runTimer(TimerId.ClientMetadataRefresh)
|
|
.expectPrimaryState(true)
|
|
.disableNetwork()
|
|
// If all tabs are offline, the primary lease is retained.
|
|
.expectPrimaryState(true)
|
|
.client(0)
|
|
.enableNetwork()
|
|
.expectPrimaryState(false)
|
|
.client(1)
|
|
.runTimer(TimerId.ClientMetadataRefresh)
|
|
// The offline primary tab releases its lease since another tab is now
|
|
// online.
|
|
.expectPrimaryState(false)
|
|
.client(0)
|
|
.runTimer(TimerId.ClientMetadataRefresh)
|
|
.expectPrimaryState(true)
|
|
);
|
|
});
|
|
|
|
specTest(
|
|
'clearPersistence() shuts down other clients',
|
|
['multi-client'],
|
|
() => {
|
|
return client(0)
|
|
.becomeVisible()
|
|
.client(1)
|
|
.client(2)
|
|
.client(0)
|
|
.shutdown()
|
|
.clearPersistence()
|
|
.client(1)
|
|
.expectIsShutdown()
|
|
.client(2)
|
|
.expectIsShutdown();
|
|
}
|
|
);
|
|
});
|