189 lines
6.7 KiB
JavaScript
189 lines
6.7 KiB
JavaScript
|
/* @odoo-module */
|
||
|
|
||
|
import { startServer } from "@bus/../tests/helpers/mock_python_environment";
|
||
|
|
||
|
import { VoicePlayer } from "@mail/discuss/voice_message/common/voice_player";
|
||
|
import { VoiceRecorder } from "@mail/discuss/voice_message/common/voice_recorder";
|
||
|
import { Command } from "@mail/../tests/helpers/command";
|
||
|
import { mockGetMedia, start } from "@mail/../tests/helpers/test_utils";
|
||
|
|
||
|
import { browser } from "@web/core/browser/browser";
|
||
|
import { Deferred } from "@web/core/utils/concurrency";
|
||
|
import { url } from "@web/core/utils/urls";
|
||
|
import { patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||
|
import { click, contains, createFile } from "@web/../tests/utils";
|
||
|
|
||
|
QUnit.module("voice message");
|
||
|
|
||
|
let audioProcessor;
|
||
|
|
||
|
function patchAudio() {
|
||
|
const {
|
||
|
AnalyserNode,
|
||
|
AudioBufferSourceNode,
|
||
|
AudioContext,
|
||
|
AudioWorkletNode,
|
||
|
GainNode,
|
||
|
MediaStreamAudioSourceNode,
|
||
|
} = browser;
|
||
|
Object.assign(browser, {
|
||
|
AnalyserNode: class {
|
||
|
connect() {}
|
||
|
disconnect() {}
|
||
|
},
|
||
|
AudioBufferSourceNode: class {
|
||
|
buffer;
|
||
|
constructor() {}
|
||
|
connect() {}
|
||
|
disconnect() {}
|
||
|
start() {}
|
||
|
stop() {}
|
||
|
},
|
||
|
AudioContext: class {
|
||
|
audioWorklet;
|
||
|
currentTime;
|
||
|
destination;
|
||
|
sampleRate;
|
||
|
state;
|
||
|
constructor() {
|
||
|
this.audioWorklet = {
|
||
|
addModule(url) {},
|
||
|
};
|
||
|
}
|
||
|
close() {}
|
||
|
/** @returns {AnalyserNode} */
|
||
|
createAnalyser() {
|
||
|
return new browser.AnalyserNode();
|
||
|
}
|
||
|
/** @returns {AudioBufferSourceNode} */
|
||
|
createBufferSource() {
|
||
|
return new browser.AudioBufferSourceNode();
|
||
|
}
|
||
|
/** @returns {GainNode} */
|
||
|
createGain() {
|
||
|
return new browser.GainNode();
|
||
|
}
|
||
|
/** @returns {MediaStreamAudioSourceNode} */
|
||
|
createMediaStreamSource(microphone) {
|
||
|
return new browser.MediaStreamAudioSourceNode();
|
||
|
}
|
||
|
/** @returns {AudioBuffer} */
|
||
|
decodeAudioData(...args) {
|
||
|
return new AudioContext().decodeAudioData(...args);
|
||
|
}
|
||
|
},
|
||
|
AudioWorkletNode: class {
|
||
|
port;
|
||
|
constructor(audioContext, processorName) {
|
||
|
this.port = {
|
||
|
onmessage(e) {},
|
||
|
postMessage(data) {
|
||
|
this.onmessage({ data, timeStamp: new Date().getTime() });
|
||
|
},
|
||
|
};
|
||
|
audioProcessor = this;
|
||
|
}
|
||
|
connect() {
|
||
|
this.port.postMessage();
|
||
|
}
|
||
|
disconnect() {}
|
||
|
process(allInputs) {
|
||
|
const inputs = allInputs[0][0];
|
||
|
this.port.postMessage(inputs);
|
||
|
return true;
|
||
|
}
|
||
|
},
|
||
|
GainNode: class {
|
||
|
connect() {}
|
||
|
close() {}
|
||
|
disconnect() {}
|
||
|
},
|
||
|
MediaStreamAudioSourceNode: class {
|
||
|
connect(processor) {}
|
||
|
disconnect() {}
|
||
|
},
|
||
|
});
|
||
|
return () => {
|
||
|
Object.assign(browser, {
|
||
|
AnalyserNode,
|
||
|
AudioBufferSourceNode,
|
||
|
AudioContext,
|
||
|
AudioWorkletNode,
|
||
|
GainNode,
|
||
|
MediaStreamAudioSourceNode,
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
|
||
|
QUnit.test("make voice message in chat", async () => {
|
||
|
const file = await createFile({
|
||
|
content: Array(500).map(() => new Int8Array()), // some non-empty content
|
||
|
contentType: "audio/mp3",
|
||
|
name: "test.mp3",
|
||
|
size: 25_000,
|
||
|
});
|
||
|
const voicePlayerDrawing = new Deferred();
|
||
|
patchWithCleanup(VoiceRecorder.prototype, {
|
||
|
_encode() {},
|
||
|
_getEncoderBuffer() {
|
||
|
return Array(500).map(() => new Int8Array());
|
||
|
},
|
||
|
_makeFile() {
|
||
|
return file;
|
||
|
},
|
||
|
});
|
||
|
patchWithCleanup(VoicePlayer.prototype, {
|
||
|
async drawWave(...args) {
|
||
|
voicePlayerDrawing.resolve();
|
||
|
return super.drawWave(...args);
|
||
|
},
|
||
|
async fetchFile() {
|
||
|
return super.fetchFile(url("/mail/static/src/audio/call_02_in_.mp3"));
|
||
|
},
|
||
|
});
|
||
|
mockGetMedia();
|
||
|
const cleanUp = patchAudio();
|
||
|
const pyEnv = await startServer();
|
||
|
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||
|
const channelId = pyEnv["discuss.channel"].create({
|
||
|
channel_member_ids: [
|
||
|
Command.create({ partner_id: pyEnv.currentPartnerId }),
|
||
|
Command.create({ partner_id: partnerId }),
|
||
|
],
|
||
|
channel_type: "chat",
|
||
|
});
|
||
|
const { openDiscuss } = await start();
|
||
|
openDiscuss(channelId);
|
||
|
await contains("button[title='Voice Message']");
|
||
|
patchDate(2023, 6, 31, 13, 0, 0, 0);
|
||
|
await click("button[title='Voice Message']");
|
||
|
await contains(".o-mail-VoiceRecorder", { text: "00 : 00" });
|
||
|
/**
|
||
|
* Simulate 10 sec elapsed.
|
||
|
* `patchDate` does not freeze the time, it merely changes the value of "now" at the time it was
|
||
|
* called. The code of click following the first `patchDate` doesn't actually happen at the time
|
||
|
* that was specified, but few miliseconds later (8 ms on my machine).
|
||
|
* The process following the next `patchDate` is intended to be between 10s and 11s later than
|
||
|
* the click, because the test wants to assert a 10 sec counter, and the two dates are
|
||
|
* substracted and then rounded down in the code (it means absolute values are irrelevant here).
|
||
|
* The problem with aiming too close to a 10s difference is that if the click is longer than
|
||
|
* the following process, it will round down to 9s.
|
||
|
* The problem with aiming too close to a 11s difference is that if the click is shorter than
|
||
|
* the following process, it will round down to 11s.
|
||
|
* The best bet is therefore to use 10s + 500ms difference.
|
||
|
*/
|
||
|
patchDate(2023, 6, 31, 13, 0, 10, 500);
|
||
|
// simulate some microphone data
|
||
|
audioProcessor.process([[new Float32Array(128)]]);
|
||
|
await contains(".o-mail-VoiceRecorder", { text: "00 : 10" });
|
||
|
await click("button[title='Stop Recording']");
|
||
|
await contains(".o-mail-VoicePlayer");
|
||
|
// wait for audio stream decode + drawing of waves
|
||
|
await voicePlayerDrawing;
|
||
|
await contains(".o-mail-VoicePlayer button[title='Play']");
|
||
|
await contains(".o-mail-VoicePlayer canvas", { count: 2 }); // 1 for global waveforms, 1 for played waveforms
|
||
|
await contains(".o-mail-VoicePlayer", { text: "00 : 04" }); // duration of call_02_in_.mp3
|
||
|
await contains(".o-mail-VoiceRecorder button[title='Voice Message']:disabled");
|
||
|
cleanUp();
|
||
|
});
|