AgoraRTCException: AgoraRTCError INVALID_OPERATION: Can't unpublish stream, haven't joined yet - agora.io

i'm stuck creating video call leave functionality in agora.
on
await agoraEngine.unpublish(localUser?.localVideoTrack)
I'm getting this error .
AgoraRTCException: AgoraRTCError INVALID_OPERATION: Can't unpublish stream, haven't joined yet!
here is my source code
import AgoraRTC, {
ICameraVideoTrack,
IMicrophoneAudioTrack,
IRemoteAudioTrack,
IRemoteVideoTrack,
} from "agora-rtc-sdk-ng"
import { useState } from "react"
export interface ChannelParameter {
remoteVideoTrack: IRemoteVideoTrack | undefined
remoteAudioTrack: IRemoteAudioTrack | undefined
remoteUid: string | undefined
localAudioTrack: IMicrophoneAudioTrack | undefined
localVideoTrack: ICameraVideoTrack | undefined
}
export interface LocalUser {
localAudioTrack: IMicrophoneAudioTrack | undefined
localVideoTrack: ICameraVideoTrack | undefined
}
export interface RemoteUser {
remoteAudioTrack: IRemoteAudioTrack | undefined
remoteVideoTrack: IRemoteVideoTrack | undefined
remoteUid: string
}
const useRoomView = () => {
const options = {
appId: "371984bde65b4dde8a0f660df398085b",
channel: "test",
token:
"007eJxTYHiS5nc04Nys5rUrD6a3TXJ6wyyhezphipWJm3OjxrtpK18qMBibG1pamCSlpJqZJpmkpKRaJBqkmZkZpKQZW1oYWJgmfX5dm9wQyMjw50I5MyMDBIL4LAwlqcUlDAwAq0Uhmw==",
}
const [localUser, setLocalUser] = useState<LocalUser>()
const [remoteUser, setRemoteUser] = useState<RemoteUser>()
const agoraEngine = AgoraRTC.createClient({
mode: "rtc",
codec: "vp8",
})
agoraEngine.on("user-unpublished", () => console.error("niiice"))
agoraEngine.on("user-published", async (user, mediaType) => {
await agoraEngine.subscribe(user, mediaType)
if (mediaType == "video") {
setRemoteUser({
remoteAudioTrack: user.audioTrack,
remoteVideoTrack: user.videoTrack,
remoteUid: user.uid.toString(),
})
}
if (mediaType == "audio") {
// user?.audioTrack?.play()
}
agoraEngine.on("user-left", user => {
console.error(user.uid + "has left the channel")
})
})
const joinCall = async () => {
agoraEngine
.join(options.appId, options.channel, options.token)
.then(uid =>
Promise.all([AgoraRTC.createMicrophoneAndCameraTracks(), uid])
)
.then(([tracks]) => {
agoraEngine.publish(tracks)
setLocalUser({
localAudioTrack: tracks[0],
localVideoTrack: tracks[1],
})
})
}
const leaveCall = async () => {
await agoraEngine.unpublish(localUser?.localVideoTrack)
await agoraEngine.unpublish(localUser?.localAudioTrack)
}
return { joinCall, localUser, remoteUser, leaveCall }
}
export default useRoomView
I was tring to make video call leave functionality using agora .

Related

React Native: How to add items to the set state of useState for getting socket notifications?

I need a function for getting notification from socket (TypeScript) .
For instance ,
when a user click the "Like",the receiver will receive a notice like "You have receive a like from XXX",and I am able to get this message from the below code ,however ,I am not sure how to save those notifications into a list in order to display all the notices ..Could you please take a look how to do it ? Thank you so much in advance !!
I have putted the socket in the useContext :
import React from 'react';
import socketio from 'socket.io-client';
export const socket = socketio.connect(SOCKET_URL);
export const SocketContext = React.createContext();
When I send click a like, the receiver can receive my notification, and then the remark in the below codes,I can't get the notification list :
import {SocketContext} from '../../auth/context';
import React, {useEffect, useState, useContext, useLayoutEffect} from 'react';
const Home = () =>{
const {socket, user} = useContext(SocketContext);
const [notificationCount, setNotificationCount] = useState([]);
const [me, setMe] = useState({});
// init data
const initialData = async () => {
try {
const meResult = await fetchGetMe();
setMe(meResult?.data.data);
} catch (error) {
console.log('initial data get errors:', error);
}
};
useLayoutEffect(() => {
initialData();
}, []);
//get feedback from socket
useEffect(() => {
socket.on('getNotification', data => {
setNotificationCount(pre=> [...pre, data]); //I got the problem here..Can't get the list
console.log('notification data :', notificationCount);
});
return () => {
socket.off('getNotification');
};
}, [socket]);
const onPressLike = ()=>{
socket.emit('sendNotification', {
senderUserId: me?.userId,
senderName: me?.userName,
receiverUserId: 123456,
type: 0, // 0:like 1.gifts 2.sunflower
});
}
<Button onClick={onPressLike}>Like</Button>
}
3.My socket config in the server part :
let onlineUsers = [];
const addUsers = (userId, socketId) => {
!onlineUsers.some((m) => m.userId !== userId) &&
onlineUsers.push({ userId, socketId });
};
const removeUser = (socketId) => {
onlineUsers = onlineUsers.filter((user) => user.socketId !== socketId);
};
const getUser = (receiverId) => {
return onlineUsers.find((m) => m.userId === receiverId);
};
io.on("connection", (socket) => {
console.log("connect now");
socket.on("newUser", (userId) => {
addUsers(userId, socket.id);
console.log("onlineUsers:", onlineUsers);
});
socket.on(
"sendNotification",
({ senderUserId, senderName, receiverUserId, type }) => {
console.log(
`senderName:${senderName},receiverID:${receiverUserId};type:${type},socketId:${socket.id};senderUserId:${senderUserId}`
);
console.log("sendNotification,onlineUsers:", onlineUsers);
let receiver = {};
if (onlineUsers.length > 0) {
receiver = getUser(senderUserId);
console.log("receiver:", receiver);
io.to(receiver.socketId).emit("getNotification", {
senderName,
type,
});
} else {
receiver = null;
console.log("receiver:", receiver);
socket.emit("getNotification", { receiver });
}
}
);
socket.on("disconnect", () => {
console.log("disconnect");
});
});

Testing custom hook - not wrapped in act warning

I' trying to test a custom hook but I receive this warning message
console.error node_modules/#testing-library/react-hooks/lib/core/console.js:19
Warning: An update to TestComponent inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser.
This is my custom hook
import { useState, useEffect } from 'react'
import io from 'socket.io-client'
import config from './../../../../config'
const useNotificationsSocket = (user) => {
const [socket, setSocket] = useState(null)
const [numUnreadMessages, setNumUnreadMessages] = useState(0)
const configureSocket = socket => {
socket.on('connect', () => {
const data = {
user: user,
}
socket.emit('user joined', data)
})
socket && socket.on('messages updated', (data) => {
//console.log(data)
setNumUnreadMessages(data.numUnreadMessages)
})
}
useEffect(() => {
const fetchSocket = async () => {
const s = await io(config.nSocket.url, {transports: ['websocket']})
configureSocket(s)
setSocket(s)
}
// Check that user is not an empty object as this causes a crash.
user && user.Id && fetchSocket()
}, [user])
return [socket, numUnreadMessages]
}
export { useNotificationsSocket }
and this is the test
import { renderHook, act } from '#testing-library/react-hooks'
import { useNotificationsSocket } from './../hooks/useNotificationsSocket'
jest.mock('socket.io-client')
describe('useNotificationsSocket', () => {
it('returns a socket and numUnreadMessages', async () => {
const user = { Id: '1' }
const { result } = renderHook(() => useNotificationsSocket(user))
expect(result).not.toBeNull()
})
})
I've tried importing act and wrapping the code in a call to act but however I try to wrap the code I still get a warning and can't figure out how I should use act in this case.
Your hook is asynchronous, so you need to await its response:
describe('useNotificationsSocket', () => {
it('returns a socket and numUnreadMessages', async () => {
const user = { Id: '1' }
const { result } = renderHook(() => useNotificationsSocket(user))
await waitFor(() => expect(result).not.toBeNull())
})
})
Additionally, if you define multiple tests, you may encounter your original error if you fail to unmount the hook. At least this appears to be the behaviour in #testing-library/react v13.3.0. You can solve this by unmounting the hook when your test completes:
describe('useNotificationsSocket', () => {
it('returns a socket and numUnreadMessages', async () => {
const user = { Id: '1' }
const { result, unmount } = renderHook(() => useNotificationsSocket(user))
await waitFor(() => expect(result).not.toBeNull())
unmount()
})
})

vue3 testing library - How to use globalProperties in tests

I am new to Vue and followed the recommendation to use vue testing library. The only issue is I can't seem to find a way to inject my code into globalProperties in render function.
Does anyone know of an example where I can inject or mock it out?
main.js
app.config.globalProperties.$globals = globalMethods
...
const app = createApp(App)
app.config.globalProperties.$globals = globalMethods
app.config.globalProperties.$globalVars = globalVars
app.component("font-awesome-icon", fontawesome)
app.use(applicationStore);
app.use (Hotjar, hotjarConfig)
app.use(i18n)
app.use(router)
app.mount('#app')
From my vue component in create I am able to call
Component.vue
let formatedObj = this.$globals.maskValues(this.inputValue, this.inputType, this);
...
,
created() {
let formatedObj = this.$globals.maskValues(this.inputValue, this.inputType, this);
this.myInputValue = formatedObj.formatedString;
this.formatedCharacterCount = formatedObj.formatedCharacterCount;
this.prevValue = this.myInputValue;
},
...
tesst.spec.js
import { render } from '#testing-library/vue'
import FormatedNumericInput from '#/components/Component.vue'
import {globalMethods} from'#/config/global-methods'
const label = 'Price'
const initSettings = {
props: {
inputId: 'testInputId1',
labelTxt: label
}
};
beforeEach(() => {
});
test('a simple string that defines your test', () => {
const { getByLabelText } = render(FormatedNumericInput, initSettings)
const input = getByLabelText(label)
// testing logic
expect(input != null).toBe(true)
expect(FormatedNumericInput != null).toBe(true)
})
** ERROR **
TypeError: Cannot read property 'maskValues' of undefined
85 | },
86 | created() {
> 87 | let formatedObj = this.$globals.maskValues(this.inputValue, this.inputType, this);
| ^
88 | this.myInputValue = formatedObj.formatedString;
89 | this.formatedCharacterCount = formatedObj.formatedCharacterCount;
90 | this.prevValue = this.myInputValue;
at Proxy.created (src/components/FormatedNumericInput.vue:87:37)
The second argument of render() is passed to #vue/test-utils mount(), so you could include the global.mocks mounting option to mock $globals.maskValues:
const { getByLabelText } = render(FormatedNumericInput, {
...initSettings,
global: {
mocks: {
$globals: {
maskValues: (inputValue, inputType) => {
const formatedString = globalFormatValue(inputValue) // declared elsewhere
return {
formatedString,
formatedCharacterCount: formatedString.length,
}
}
}
}
}
})
This is my solution in actual Vue3/Vite/Vitest environment, I set some mocks globally, so I don't need to in every test suite.
// vitest.config.ts
import { mergeConfig } from 'vite';
import { defineConfig } from 'vitest/config';
import viteConfig from './vite.config';
export default defineConfig(
mergeConfig(viteConfig, { // extending app vite config
test: {
setupFiles: ['tests/unit.setup.ts'],
environment: 'jsdom',
}
})
);
// tests/unit.setup.ts
import { config } from "#vue/test-utils"
config.global.mocks = {
$t: tKey => tKey; // just return translation key
};
so for you it will be something like
config.global.mocks = {
$globals: {
maskValues: (inputValue, inputType) => {
// ...implementation
return {
formatedString,
formatedCharacterCount,
}
}
}
}

TypeError: Cannot read property 'addEventListener' of null when creating unit test - Jasmine (Angular)

I have unit tests with Jasmine and what I'm getting is this
TypeError: Cannot read property 'addEventListener' of null
The actual code is this...
ngAfterViewInit() {
this.autoCompleteInput = <HTMLInputElement>document.querySelector('.search-input');
this.autoCompleteInput.addEventListener('blur', this.onBlur.bind(this));
this.autoCompleteInput.addEventListener('input', this.onInput.bind(this));
this.autoCompleteInput.addEventListener('focus', this.onFocus.bind(this));
this.renderer.setAttribute(this.inputRef.nativeElement, 'aria-autocomplete', 'both');
if (this.filter !== undefined && this.filter !== null && this.filter !== '') {
this.filter = '';
}
}
The first line of the addEventListener works
this.autoCompleteInput.addEventListener('blur', this.onBlur.bind(this));
but the remaining fail with ng test --code-coverage --watch=false
When I check the report I see this:
What I don't understand is why onBlur tests fine and the rest do not?
Here's my Jasmine code:
import { TestBed, async, ComponentFixture, fakeAsync, tick } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '#angular/core';
import { AutocompleteComponent } from './autocomplete.component';
import { filter } from 'lodash';
describe('Auto Complete Component', () => {
let autoCompleteComponent: AutocompleteComponent;
let fixture: ComponentFixture<AutocompleteComponent>;
let autoCompleteInput: HTMLInputElement;
let filteredItems: string[] = [];
let $window, $provide, listeners;
// let rendered: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([])
],
declarations: [
AutocompleteComponent
],
providers: [AutocompleteComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(AutocompleteComponent);
autoCompleteComponent = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('should create call ngOnChanges ', () => {
jasmine.createSpy('ngOnChanges').and.callThrough();
autoCompleteComponent.ngOnChanges();
expect(autoCompleteComponent.ngOnChanges()).toHaveBeenCalled();
});
it('should create filterItems() Function ', () => {
jasmine.createSpy('filterItems').and.callThrough();
expect(autoCompleteComponent.filterItems).toBeUndefined();
});
it('should create call clearFocus ', () => {
jasmine.createSpy('clearFocus').and.callThrough();
autoCompleteComponent.clearFocus();
expect(autoCompleteComponent.clearFocus()).toHaveBeenCalled();
});
it('should call onBlur Event ', () => {
jasmine.createSpy('onBlur').and.callThrough();
autoCompleteComponent.onBlur(event);
expect(autoCompleteComponent.onBlur(event)).toHaveBeenCalled();
});
it('should call onItemSelect Event ', () => {
let item = '';
jasmine.createSpy('onItemSelect').and.callThrough();
jasmine.createSpy('clearFocus').and.callThrough();
expect(autoCompleteComponent.onItemSelect(event, item)).toHaveBeenCalled();
expect(autoCompleteComponent.itemSelect.emit(item)).toHaveBeenCalled();
expect(autoCompleteComponent.clearFocus()).toHaveBeenCalled();
});
it('should call onFocus Event ', () => {
jasmine.createSpy('onFocus').and.callThrough();
autoCompleteComponent.onFocus(event);
expect(autoCompleteComponent.autoCompleteInput.focus()).toHaveBeenCalled;
// expect(autoCompleteComponent.onFocus(event)).toHaveBeenCalled();
});
it('should call onInput Event ', () => {
jasmine.createSpy('onInput').and.callThrough();
autoCompleteComponent.onInput(event);
expect(autoCompleteComponent.onInput(event)).toHaveBeenCalled();
});
});
After much experimentation, I found the answer and I hope this helps others.
Here's the solution:
ngAfterViewInit() {
this.autoCompleteInput = <HTMLInputElement>document.querySelector('.search-input');
console.log('Add Event Listener: ', this.autoCompleteInput);
this.bindOnBlurStateEventCallback();
this.bindOnInputStateEventCallback();
this.bindOnFocusStateEventCallback();
this.renderer.setAttribute(this.autoCompleteInput, 'aria-autocomplete', 'both');
if (this.filter !== undefined && this.filter !== null && this.filter !== '') {
this.filter = '';
}
}
public bindOnBlurStateEventCallback(): void {
this.autoCompleteInput.addEventListener('blur', this.onBlur.bind(this));
document.querySelector('.search-input').addEventListener('blur', () => {
console.log('You selected: ', this.autoCompleteInput.value);
});
}
public bindOnInputStateEventCallback(): void {
this.autoCompleteInput.addEventListener('input', this.onInput.bind(this));
}
public bindOnFocusStateEventCallback(): void {
this.autoCompleteInput.addEventListener('focus', this.onFocus.bind(this));
}
and in the spec.ts file:
it('adds listener events', function () {
spyOn(document, 'addEventListener').and.callThrough();
spyOn(window, 'addEventListener').and.callThrough();
expect(document.addEventListener.prototype).not.toBeTruthy;
expect(window.addEventListener.prototype).not.toBeTruthy;
expect(document.addEventListener.prototype).toBeTruthy;
expect(window.addEventListener.prototype).toBeTruthy;
});
And there you have it!

ngrx store state undefined

I am not sure why my state in my store is undefined when I try to access it. I have been looking at this for sometime now and I cannot figure it out.
my actions are
export const GetMerchants = createAction('[Merchant] - Get Merchants');
export const GetMerchantsSuccess = createAction(
'[Merchant] - Get Merchants Success',
props<{ payload: Merchant[] }>()
);
export const GetMerchantsFailure = createAction(
'[Merchant] - Get Merchants Failure',
props<{ payload: Error }>()
);
My reducers and state def are
export default class MerchantListState {
merchants: Array<Merchant>;
merchantError: Error;
}
export const initializeMerchantListState = (): MerchantListState => {
return {
merchants: new Array<Merchant>(),
merchantError: null
};
};
export const intialMerchantListState = initializeMerchantListState();
const _reducer = createReducer(
intialMerchantListState,
on(actions.GetMerchants, (state: MerchantListState) => {
return {
...state
};
}),
on(actions.GetMerchantsSuccess, (state: MerchantListState, { payload }) => {
let newstate = { ...state,
merchants: [ ...state.merchants, payload],
merchantError: null
};
return newstate;
}),
on(actions.GetMerchantsFailure, (state: MerchantListState, { payload }) => {
console.log(payload);
return { ...state, merchantError: payload };
}),
);
export function merchantListReducer(state: MerchantListState, action: Action) {
return _reducer(state, action);
}
My effects
#Injectable()
export class MerchantListEffects {
constructor(private apiService: ApiService, private apiRouteService: ApiRouteService, private action$: Actions) { }
GetMerchants$: Observable<Action> = createEffect(() =>
this.action$.pipe(
ofType(actions.GetMerchants),
mergeMap(action => this.apiService.get(this.apiRouteService.toMerchants()).pipe(
map((data: Merchant[]) => { console.log(data); return actions.GetMerchantsSuccess({ payload: data }); }
), catchError((error: Error) => { return of(actions.GetMerchantsFailure({ payload: error })) })
)
)));
}
When I inject the state into the component
private store: Store<{ merchantList: MerchantListState }>
I get an undefined merchant$ observable when I try to do this
this.merchants$ = store.pipe(select('merchantList'));
this.merchantSubscription = this.merchants$.pipe(
map(x => {
console.log(x.merchants);
})
)
.subscribe();
On a button click I am loading the merchants with this dispatch
this.store.dispatch(actions.GetMerchants());
I have my reducer and effects defined in AppModule
StoreModule.forRoot({ merchantList: merchantListReducer }),
EffectsModule.forRoot([MerchantListEffects])
Is it something that I am missing?
First Parameter of createReducer is a value, not a function.
API > #ngrx/store
createReducer
If you use a function, you have to call it:
const _reducer = createReducer(
intialMerchantListState()
I prefare the way to define direct a value initialState:
export const initializeMerchantListState: MerchantListState = {
merchants: new Array<Merchant>(),
merchantError: null
};