In Agora GroupVoice call mouse click event not working after user joining as host or audience - agora.io

I am trying to build voice chat using creatstream and mode live in nextjs project. But experiencing some unsual behaviour. After the call started if there is more than one host or someone leave and join , loosing the mouse click event on the screen. Which means cannot host/audience can’t mute/unmute or leave the call. But can do the same with the keyboard controls. Could someone please explain, what is that i am missing. I am attaching the code of agora for host. It will be great if someone can help me to understand the problem. We are also generating RTCtoken. Thanks in advance!
function AgoraVoiceCall({ channel, attendeeMode, appId, uid, user }) {
console.log(attendeeMode);
const [client, setClient] = useState(null);
const [localStream, setLocalStream] = useState(null);
//Mutation
const [generateAgoraToken, { data, loading }] = useMutation(
GENERATE_SPACE_TOKEN
);
var options = {
token: undefined,
uid: Math.floor(Math.random() * Date.now() * 0.001).toString(),
};
const join = async () => {
let clientInstance = Agora.createClient({ mode: 'live', codec: 'vp8' });
clientInstance.setClientRole(attendeeMode);
const token = await generateAgoraToken({
variables: { channelName: channel, uid: options.uid, role: attendeeMode },
});
(options.token = token && token.data.generateAgoraToken),
console.log('token', token.data.generateAgoraToken);
clientInstance.init(appId, () => {
clientInstance.join(options.token, channel, options.uid, uid => {
let localStreamInstance = Agora.createStream({
streamID: uid,
audio: true,
video: false,
screen: false,
});
setLocalStream(localStreamInstance);
localStreamInstance.init(() => {
clientInstance.publish(localStreamInstance);
const div = document
.getElementById('local_stream')
.insertAdjacentHTML(
'afterBegin',
`<div id="player-wrapper-${options.uid}">
<p class="player-name">LocalUser(${options.uid})</p>
</div>`
);
localStreamInstance.play('local_stream');
});
clientInstance.on('stream-added', evt => {
let remoteStream = evt.stream;
const id = remoteStream.getId();
const div = document
.getElementById('remote_stream')
.insertAdjacentHTML(
'afterBegin',
`<div id="player-wrapper-${id}">
<p class="player-name">RemoteUser(${id})</p>
</div>`
);
console.log(div);
clientInstance &&
clientInstance.subscribe(remoteStream, function(err) {
console.log('Subscribe stream failed', err);
});
});
clientInstance.on('stream-subscribed', evt => {
let remoteStream = evt.stream;
remoteStream.play('remote_stream');
});
clientInstance.on('stream-unpublished', evt => {
console.log('peer-leave', evt.stream);
let remoteStream = evt.stream;
const id = remoteStream.getId();
delete remoteUsers[id];
const remoteUserContainer = document.getElementById(
`player-wrapper-${id}`
);
remoteUserContainer.remove();
console.log(remoteUsers);
setRemoteUsersData(remoteUsers);
});
});
});
setClient(clientInstance);
};
const leaveCall = () => {
// Destroy the local audio and track.
client && client.unpublish(localStream);
localStream && localStream.close();
const id = localStream.getId();
const localStreamContainer = document.getElementById(
`player-wrapper-${id}`
);
localStreamContainer && localStreamContainer.remove();
// Leave the channel.
client &&
client.leave(
() => {
console.log('Client succeed to leave.');
},
() => {
console.log('Client failed to leave.');
}
);
setLocalStream(null);
};
const handleMic = () => {
const btn = document.getElementById('mic-btn');
if (localStream.isAudioOn()) {
localStream.muteAudio();
btn.innerHTML = 'UNMUTE';
} else {
localStream.unmuteAudio();
btn.innerHTML = 'MUTE';
}
};
return (
<Wrapper>
<div
style={{
display: 'flex',
flexDirection: 'column',
margin: '0 auto',
height: '100vh',
width: '60vw',
backgroundColor: 'lightblue',
}}
>
<h2>Welcome to Neospace Voice Call</h2>
<div
style={{
height: '30vh',
backgroundColor: 'pink',
border: '3px solid black',
}}
id="local_stream"
>
<Avatar src={user.avatarUrl ?? '/avatar_placeholder.svg'} />
</div>
<div
style={{
height: '30vh',
backgroundColor: 'yellow',
border: '3px solid black',
}}
id="remote_stream"
>
<Avatar src={user.avatarUrl ?? '/avatar_placeholder.svg'} />
</div>
<div
style={{
height: '20vh',
backgroundColor: 'orange',
border: '3px solid black',
}}
>
control
<div
style={{
margin: '5vh 0 0 30vh',
}}
>
{/* All these button onclick with the mouse is not working */}
<button
onClick={join}
style={{ marginRight: '15px', fontSize: '20px' }}
disabled={localStream}
>
JOIN CALL
</button>
<button
id="mic-btn"
onClick={e => {
handleMic(e);
}}
style={{ marginRight: '15px', fontSize: '20px' }}
>
MUTE
</button>
<button onClick={leaveCall} style={{ fontSize: '20px' }}>
END CALL
</button>
</div>
</div>
</div>
</Wrapper>
);
}
export default withApollo(AgoraVoiceCall);

From your explanation it sounds like a UI problem, I'd just logging out the click event to see if it's a problem with the UI.

Related

INVALID_STATE_ERR (React native)

What i am trying to do is simple. But not having enough knowledge about react state caused the problem. In my tab i took typing useState as false. I am trying to get the state when user is typing. By connecting websocket when user is typing i am sending type: "is_typing" data. And when receive is_typing i am trying to change the typing state to true and set timeout 500ms so that it won't show typing if it doesn't get onkeypress.
const Sock = () => {
const [typing, setTyping] = useState(false)
const url = endpoint + "chat/" + "thorOdin" + "/" + "?token=" + token
const ws = new WebSocket(url)
useEffect(() => {
if (token) {
ws.onopen = function () {
console.log("Chat Websoket Connected");
};
ws.onmessage = function (event) {
const data = JSON.parse(event.data);
if (data.command === "is_typing") {
setTyping(true)
setTimeout(() => {
setTyping(false)
}, 500);
}
};
ws.onerror = (e) => {
// an error occurred
console.log(e.message);
};
ws.onclose = function () {
console.log("WebSocket Client disconnected");
};
}
}, []);
function typingHandle(e){
ws.send(
JSON.stringify({
command: "is_typing",
text: `is typing ...`,
user: "me",
})
);
}
return (
<View>
{typing==true?
<Text style={{marginTop:50}}>Typing</Text>:
<View></View>
}
<TextInput placeholderTextColor={'gray'} style={{
color: 'white',
backgroundColor: '#262626',
borderRadius: 20,
paddingLeft: 10,
height: 40,
width: '80%',
marginLeft: 10,
marginTop:100
}} placeholder='Write Message..'
onKeyPress={typingHandle}/>
</View>
)
}
It's working. But after 4-5 sec it showing Uncaught error "INVALID_STATE_ERR". Can anyone tell what am i doing wrong here???
I am trying to get typing state of user through socket connection.

connecting 2 react native pickers

I need to connect a react native picker select element to another. I mean I have two models name Brand and Model. When I select Brand, the Model picker should fill with models which are belongs to this brand. I tried something like below, but I get "undefined is not a function (near '...models.map...')" error. How should I connect two pickers like this? Or where is my mistake?
class Brand(models.Model):
brand_name = models.CharField(max_length=20, blank = True, default = '')
class Model(models.Model):
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
model_name = models.CharField(max_length = 20, blank = True, default = '')
class BrandViewSet(viewsets.ModelViewSet):
serializer_class = BrandSerializer
queryset = Brand.objects.all()
permission_classes = (AllowAny,)
#action(detail=True, methods=['GET'])
def getbrandmodels(self, request, pk=None):
brand = Brand.objects.get(id=pk)
models = Model.objects.filter(brand=brand)
serializer = ModelSerializer(data=models)
serializer.is_valid()
return Response(serializer.data)
const [brands, setBrands] = useState([]);
const [models, setModels] = useState([]);
const [brand, setBrand] = useState(1);
const [model, setModel] = useState(1);
useEffect(() => {
getBrands();
getmodels();
}, []);
const getBrands = () => {
fetch('http://127.0.0.1:8000/api/brands/', {
method: 'GET',
headers: {
}
})
.then( res => res.json())
.then( jsonRes => setBrands(jsonRes))
.catch( error => console.log(error));
}
const getmodels = (value) => {
setBrand(value);
fetch(`http://127.0.0.1:8000/api/brands/${brand}/getbrandmodels`, {
method: 'GET',
headers: {
}
})
.then( res => res.json())
.then( jsonRes => setModels(jsonRes))
.catch( error => console.log(error));
}
return (
<View style={styles.container}>
<RNPickerSelect style={StyleSheet.flatten(styles.picker)} itemStyle={styles.pickerItem}
useNativeAndroidPickerStyle={false}
selectedValue={brand}
onValueChange={value=>getmodels(value)}
doneText = 'Tamam'
items={brands.map(obj => (
{
key: obj.id,
label: obj.brand_name,
value: obj.id,
color: "rgba(77,38,22,1)",
}))}
>
</RNPickerSelect>
<RNPickerSelect style={StyleSheet.flatten(styles.picker)} itemStyle={styles.pickerItem}
useNativeAndroidPickerStyle={false}
selectedValue={model}
onValueChange={value=>setModel(value)}
doneText = 'Tamam'
items={models.map(obj => (
{
key: obj.id,
label: obj.model_name,
value: obj.id,
color: "rgba(77,38,22,1)",
}))}
>
</RNPickerSelect>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'orange',
padding: 5
},
label: {
fontSize: 15,
color: 'white',
padding: 10
},
input: {
fontSize: 20,
backgroundColor: '#fff',
padding: 5,
margin: 5
},
picker: {
// flex: 1,
width: "100%",
height: 44,
},
pickerItem: {
height: 44
}
});
I do not know why but again I solved it after ask here :)
I only changed my getbrandmodels method in viewset as below.
#action(detail=True, methods=['GET'])
def getbrandmodels(self, request, pk=None):
brand = Brand.objects.get(id=pk)
models = Model.objects.filter(brand=brand)
serializer = ModelSerializer(instance=models, many=True)
return Response(serializer.data)

The best way of tracking location in background using react-native + Expo in 2020

I want to create my own Endomono/Runtastic-like app using RN + expo (This app will be just for me, and I have android phone with pretty decent performance/battery life (Redmi note 7) so I don't worry about performance too much). I wanted to use all-in-one library for that, or just and library that allows me to execute some code each X seconds in background (and getAsyncLocation there). My point is just to send lat/lon data every X seconds to my backend HTTP django-rest-framework powered server.
I just spent whole day trying figure out any way to do that, I tried couple of libraries like this ones: react-native-background-geolocation, react-native-background-timer, react-native-background-job and few more. I followed step by step instalation guide, and I kept getting errors like: null is not an object (evaluating 'RNBackgroundTimer.setTimeout') .
I also tried this: I fixed some errors in this code (imports related), it seemed to work, but when I changed my GPS location using Fake GPS, and only one cast of didFocus functions appears in the console. Here's code:
import React from 'react';
import { EventEmitter } from 'fbemitter';
import { NavigationEvents } from 'react-navigation';
import { AppState, AsyncStorage, Platform, StyleSheet, Text, View, Button } from 'react-native';
import MapView from 'react-native-maps';
import * as Permissions from 'expo-permissions';
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import { FontAwesome, MaterialIcons } from '#expo/vector-icons';
const STORAGE_KEY = 'expo-home-locations';
const LOCATION_UPDATES_TASK = 'location-updates';
const locationEventsEmitter = new EventEmitter();
export default class MapScreen extends React.Component {
static navigationOptions = {
title: 'Background location',
};
mapViewRef = React.createRef();
state = {
accuracy: 4,
isTracking: false,
showsBackgroundLocationIndicator: false,
savedLocations: [],
initialRegion: null,
error: null,
};
didFocus = async () => {
console.log("Hello")
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
AppState.addEventListener('change', this.handleAppStateChange);
this.setState({
error:
'Location permissions are required in order to use this feature. You can manually enable them at any time in the "Location Services" section of the Settings app.',
});
return;
} else {
this.setState({ error: null });
}
const { coords } = await Location.getCurrentPositionAsync();
console.log(coords)
const isTracking = await Location.hasStartedLocationUpdatesAsync(LOCATION_UPDATES_TASK);
const task = (await TaskManager.getRegisteredTasksAsync()).find(
({ taskName }) => taskName === LOCATION_UPDATES_TASK
);
const savedLocations = await getSavedLocations();
const accuracy = (task && task.options.accuracy) || this.state.accuracy;
this.eventSubscription = locationEventsEmitter.addListener('update', locations => {
this.setState({ savedLocations: locations });
});
if (!isTracking) {
alert('Click `Start tracking` to start getting location updates.');
}
this.setState({
accuracy,
isTracking,
savedLocations,
initialRegion: {
latitude: coords.latitude,
longitude: coords.longitude,
latitudeDelta: 0.004,
longitudeDelta: 0.002,
},
});
};
handleAppStateChange = nextAppState => {
if (nextAppState !== 'active') {
return;
}
if (this.state.initialRegion) {
AppState.removeEventListener('change', this.handleAppStateChange);
return;
}
this.didFocus();
};
componentWillUnmount() {
if (this.eventSubscription) {
this.eventSubscription.remove();
}
AppState.removeEventListener('change', this.handleAppStateChange);
}
async startLocationUpdates(accuracy = this.state.accuracy) {
await Location.startLocationUpdatesAsync(LOCATION_UPDATES_TASK, {
accuracy,
showsBackgroundLocationIndicator: this.state.showsBackgroundLocationIndicator,
});
if (!this.state.isTracking) {
alert(
'Now you can send app to the background, go somewhere and come back here! You can even terminate the app and it will be woken up when the new significant location change comes out.'
);
}
this.setState({ isTracking: true });
}
async stopLocationUpdates() {
await Location.stopLocationUpdatesAsync(LOCATION_UPDATES_TASK);
this.setState({ isTracking: false });
}
clearLocations = async () => {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify([]));
this.setState({ savedLocations: [] });
};
toggleTracking = async () => {
await AsyncStorage.removeItem(STORAGE_KEY);
if (this.state.isTracking) {
await this.stopLocationUpdates();
} else {
await this.startLocationUpdates();
}
this.setState({ savedLocations: [] });
};
onAccuracyChange = () => {
const next = Location.Accuracy[this.state.accuracy + 1];
const accuracy = next ? Location.Accuracy[next] : Location.Accuracy.Lowest;
this.setState({ accuracy });
if (this.state.isTracking) {
// Restart background task with the new accuracy.
this.startLocationUpdates(accuracy);
}
};
toggleLocationIndicator = async () => {
const showsBackgroundLocationIndicator = !this.state.showsBackgroundLocationIndicator;
this.setState({ showsBackgroundLocationIndicator }, async () => {
if (this.state.isTracking) {
await this.startLocationUpdates();
}
});
};
onCenterMap = async () => {
const { coords } = await Location.getCurrentPositionAsync();
const mapView = this.mapViewRef.current;
if (mapView) {
mapView.animateToRegion({
latitude: coords.latitude,
longitude: coords.longitude,
latitudeDelta: 0.004,
longitudeDelta: 0.002,
});
}
};
renderPolyline() {
const { savedLocations } = this.state;
if (savedLocations.length === 0) {
return null;
}
return (
<MapView.Polyline
coordinates={savedLocations}
strokeWidth={3}
strokeColor={"black"}
/>
);
}
render() {
if (this.state.error) {
return <Text style={styles.errorText}>{this.state.error}</Text>;
}
if (!this.state.initialRegion) {
return <NavigationEvents onDidFocus={this.didFocus} />;
}
return (
<View style={styles.screen}>
<MapView
ref={this.mapViewRef}
style={styles.mapView}
initialRegion={this.state.initialRegion}
showsUserLocation>
{this.renderPolyline()}
</MapView>
<View style={styles.buttons} pointerEvents="box-none">
<View style={styles.topButtons}>
<View style={styles.buttonsColumn}>
{Platform.OS === 'android' ? null : (
<Button style={styles.button} onPress={this.toggleLocationIndicator} title="background/indicator">
<Text>{this.state.showsBackgroundLocationIndicator ? 'Hide' : 'Show'}</Text>
<Text> background </Text>
<FontAwesome name="location-arrow" size={20} color="white" />
<Text> indicator</Text>
</Button>
)}
</View>
<View style={styles.buttonsColumn}>
<Button style={styles.button} onPress={this.onCenterMap} title="my location">
<MaterialIcons name="my-location" size={20} color="white" />
</Button>
</View>
</View>
<View style={styles.bottomButtons}>
<Button style={styles.button} onPress={this.clearLocations} title="clear locations">
Clear locations
</Button>
<Button style={styles.button} onPress={this.toggleTracking} title="start-stop tracking">
{this.state.isTracking ? 'Stop tracking' : 'Start tracking'}
</Button>
</View>
</View>
</View>
);
}
}
async function getSavedLocations() {
try {
const item = await AsyncStorage.getItem(STORAGE_KEY);
return item ? JSON.parse(item) : [];
} catch (e) {
return [];
}
}
if (Platform.OS !== 'android') {
TaskManager.defineTask(LOCATION_UPDATES_TASK, async ({ data: { locations } }) => {
if (locations && locations.length > 0) {
const savedLocations = await getSavedLocations();
const newLocations = locations.map(({ coords }) => ({
latitude: coords.latitude,
longitude: coords.longitude,
}));
savedLocations.push(...newLocations);
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(savedLocations));
locationEventsEmitter.emit('update', savedLocations);
}
});
}
const styles = StyleSheet.create({
screen: {
flex: 1,
},
mapView: {
flex: 1,
},
buttons: {
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between',
padding: 10,
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
},
topButtons: {
flexDirection: 'row',
justifyContent: 'space-between',
},
bottomButtons: {
flexDirection: 'column',
alignItems: 'flex-end',
},
buttonsColumn: {
flexDirection: 'column',
alignItems: 'flex-start',
},
button: {
paddingVertical: 5,
paddingHorizontal: 10,
marginVertical: 5,
},
errorText: {
fontSize: 15,
color: 'rgba(0,0,0,0.7)',
margin: 20,
},
});
If you know any way to easily complete my target (of sending simple HTTP GET with location from background of Expo + RN app to my DRF backend) please let me know.
If you're using Expo you can simply use expo-task-manager and expo-location to get background location updates.
Here's a simplified version that I'm using (and it's working for sure on Android) on the App I'm currently developing:
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import axios from 'axios';
const TASK_FETCH_LOCATION = 'TASK_FETCH_LOCATION';
// 1 define the task passing its name and a callback that will be called whenever the location changes
TaskManager.defineTask(TASK_FETCH_LOCATION, async ({ data: { locations }, error }) => {
if (error) {
console.error(error);
return;
}
const [location] = locations;
try {
const url = `https://<your-api-endpoint>`;
await axios.post(url, { location }); // you should use post instead of get to persist data on the backend
} catch (err) {
console.error(err);
}
});
// 2 start the task
Location.startLocationUpdatesAsync(TASK_FETCH_LOCATION, {
accuracy: Location.Accuracy.Highest,
distanceInterval: 1, // minimum change (in meters) betweens updates
deferredUpdatesInterval: 1000, // minimum interval (in milliseconds) between updates
// foregroundService is how you get the task to be updated as often as would be if the app was open
foregroundService: {
notificationTitle: 'Using your location',
notificationBody: 'To turn off, go back to the app and switch something off.',
},
});
// 3 when you're done, stop it
Location.hasStartedLocationUpdatesAsync(TASK_FETCH_LOCATION).then((value) => {
if (value) {
Location.stopLocationUpdatesAsync(TASK_FETCH_LOCATION);
}
});
It doesn't necessarily work with Expo, but if "eject" your project or start with the React Native CLI (via react-native init) then you could use an Android specific React Native "NativeModule" to accomplish your goal. I like using the react-native-location package, which has great support on iOS for background location updates, but on Android there is a bug currently. I put together an example project which has the necessary Android specific code inside a NativeModule you could use to start from:
https://github.com/andersryanc/ReactNative-LocationSample

Resize feature seems to be incompatible with external drag-and-drop

First of all, I would like to express my gratitude for this repo: https://github.com/Tim1023/react-scheduler-firebase without which I would not be able to implement external drag-and-drop for react-big-calendar.
I have spent an entire week trying to play with this stuff, but resize feature (stretching the event up and down) just won't work for it. I guess the problem is with css ?
I have tried to upgrade the react-big-calendar's version from 0.19 to 0.20, and situation is the opposite - resize works perfectly, but external DnD no longer works (namely, when dragging an external object on the calendar, drop mode does not get activated)
There's an open issue regarding this, but looks like it remains unhandled up to now.
The latest version of RBC does include external drag and drop. Although the documentation site has not yet been updated, cloning the repo and running the 'examples' will show a working demo of this functionality. Here is the source for that demo:
import React from 'react'
import events from '../events'
import { Calendar, Views } from 'react-big-calendar'
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop'
import Layout from 'react-tackle-box/Layout'
import Card from '../Card'
import 'react-big-calendar/lib/addons/dragAndDrop/styles.scss'
const DragAndDropCalendar = withDragAndDrop(Calendar)
const formatName = (name, count) => `${name} ID ${count}`
class Dnd extends React.Component {
constructor(props) {
super(props)
this.state = {
events: events,
draggedEvent: null,
counters: {
item1: 0,
item2: 0,
},
displayDragItemInCell: true,
}
}
handleDragStart = event => {
this.setState({ draggedEvent: event })
}
handleDisplayDragItemInCell = () => {
this.setState({
displayDragItemInCell: !this.state.displayDragItemInCell,
})
}
dragFromOutsideItem = () => {
return this.state.draggedEvent
}
customOnDragOver = event => {
// check for undroppable is specific to this example
// and not part of API. This just demonstrates that
// onDragOver can optionally be passed to conditionally
// allow draggable items to be dropped on cal, based on
// whether event.preventDefault is called
if (this.state.draggedEvent !== 'undroppable') {
console.log('preventDefault')
event.preventDefault()
}
}
onDropFromOutside = ({ start, end, allDay }) => {
const { draggedEvent, counters } = this.state
const event = {
title: formatName(draggedEvent.name, counters[draggedEvent.name]),
start,
end,
isAllDay: allDay,
}
const updatedCounters = {
...counters,
[draggedEvent.name]: counters[draggedEvent.name] + 1,
}
this.setState({ draggedEvent: null, counters: updatedCounters })
this.newEvent(event)
}
moveEvent = ({ event, start, end, isAllDay: droppedOnAllDaySlot }) => {
const { events } = this.state
const idx = events.indexOf(event)
let allDay = event.allDay
if (!event.allDay && droppedOnAllDaySlot) {
allDay = true
} else if (event.allDay && !droppedOnAllDaySlot) {
allDay = false
}
const updatedEvent = { ...event, start, end, allDay }
const nextEvents = [...events]
nextEvents.splice(idx, 1, updatedEvent)
this.setState({
events: nextEvents,
})
// alert(`${event.title} was dropped onto ${updatedEvent.start}`)
}
resizeEvent = ({ event, start, end }) => {
const { events } = this.state
const nextEvents = events.map(existingEvent => {
return existingEvent.id == event.id
? { ...existingEvent, start, end }
: existingEvent
})
this.setState({
events: nextEvents,
})
//alert(`${event.title} was resized to ${start}-${end}`)
}
newEvent = event => {
let idList = this.state.events.map(a => a.id)
let newId = Math.max(...idList) + 1
let hour = {
id: newId,
title: event.title,
allDay: event.isAllDay,
start: event.start,
end: event.end,
}
this.setState({
events: this.state.events.concat([hour]),
})
}
render() {
return (
<div>
<Card className="examples--header" style={{ display: 'flex' }}>
<div
style={{
display: 'flex',
flex: 1,
justifyContent: 'center',
flexWrap: 'wrap',
}}
>
<h4 style={{ color: 'gray', width: '100%' }}>
Outside Drag Sources
</h4>
{Object.entries(this.state.counters).map(([name, count]) => (
<div
style={{
border: '2px solid gray',
borderRadius: '4px',
width: '100px',
margin: '10px',
}}
draggable="true"
key={name}
onDragStart={() =>
this.handleDragStart({ title: formatName(name, count), name })
}
>
{formatName(name, count)}
</div>
))}
<div
style={{
border: '2px solid gray',
borderRadius: '4px',
width: '100px',
margin: '10px',
}}
draggable="true"
key={name}
onDragStart={() => this.handleDragStart('undroppable')}
>
Draggable but not for calendar.
</div>
</div>
<div>
<label>
<input
style={{ marginRight: 5 }}
type="checkbox"
checked={this.state.displayDragItemInCell}
onChange={this.handleDisplayDragItemInCell}
/>
Display dragged item in cell while dragging over
</label>
</div>
</Card>
<DragAndDropCalendar
selectable
localizer={this.props.localizer}
events={this.state.events}
onEventDrop={this.moveEvent}
dragFromOutsideItem={
this.state.displayDragItemInCell ? this.dragFromOutsideItem : null
}
onDropFromOutside={this.onDropFromOutside}
onDragOver={this.customOnDragOver}
resizable
onEventResize={this.resizeEvent}
onSelectSlot={this.newEvent}
onD
defaultView={Views.MONTH}
defaultDate={new Date(2015, 3, 12)}
/>
</div>
)
}
}
export default Dnd

Chai testing on React App returning unexpected result

Hi I ran the following test and the test confirmed that ChannelListItem exists:
import React from 'react';
//import expect from 'expect';
import { expect } from 'chai';
import io from 'socket.io-client';
import sinon from 'sinon';
import { Provider } from 'react-redux';
import configureStore from '../../src/common/store/configureStore';
import { shallow,mount } from 'enzyme';
import Channels from '../../src/common/components/Channels';
import ChannelListItem from '../../src/common/components/ChannelListItem';
import { fakeChannels } from '../fakeData/channelsFake';
import { fakeMessages } from '../fakeData/messagesFake';
const initialState = window.__INITIAL_STATE__;
const store = configureStore(initialState);
const socket = io('', { path: '/api/chat' });
describe('Channels', () => {
const changeActiveChannel = sinon.spy()
const dispatch = sinon.spy(store, 'dispatch')
let Component;
beforeEach(() => {
Component =
shallow(<Provider store={store}>
<Channels
socket = {socket}
onClick = {changeActiveChannel}
channels = {fakeChannels}
messages = {fakeMessages}
dispatch = {dispatch}
/>
</Provider>);
});
it('should render', () => {
expect(Component).to.be.ok;
});
it('should have a ChannelListItem', () => {
const channelListItem = Component.find('ChannelListItem')
expect(channelListItem).to.exist;
However, when I ran the following test, I got channelListItem.length equal 0
expect(channelListItem.length).to.equal(3);
Any ideas what could be wrong? I clearly have a channelListItem inside my Channel component:
return (
<ChannelListItem style={{paddingLeft: '0.8em', background: '#2E6DA4', height: '0.7em'}} channel={'aa'} key={'1'} onClick={::this.handleChangeChannel} />
);
Code for Channels:
import React, { Component, PropTypes } from 'react';
import ChannelListItem from './ChannelListItem';
import ChannelListModalItem from './ChannelListModalItem';
import { Modal, Glyphicon, Input, Button } from 'react-bootstrap';
import * as actions from '../actions/actions';
import uuid from 'node-uuid';
import { createChannel } from '../reducers/channels';
export default class Channels extends Component {
static propTypes = {
channels: PropTypes.array.isRequired,
onClick: PropTypes.func.isRequired,
messages: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired
};
constructor(props, context) {
super(props, context);
this.state = {
addChannelModal: false,
channelName: '',
moreChannelsModal: false
};
}
handleChangeChannel(channel) {
if(this.state.moreChannelsModal) {
this.closeMoreChannelsModal();
}
this.props.onClick(channel);
}
openAddChannelModal(event) {
//event.preventDefault();
this.setState({addChannelModal: true});
}
closeAddChannelModal(event) {
event.preventDefault();
this.setState({addChannelModal: false});
}
handleModalChange(event) {
this.setState({channelName: event.target.value});
}
handleModalSubmit(event) {
const { channels, dispatch, socket } = this.props;
event.preventDefault();
if (this.state.channelName.length < 1) {
this.refs.channelName.getInputDOMNode().focus();
}
if (this.state.channelName.length > 0 && channels.filter(channel => {
return channel.name === this.state.channelName.trim();
}).length < 1) {
const newChannel = {
name: this.state.channelName.trim(),
id: `${Date.now()}${uuid.v4()}`,
private: false
};
dispatch(createChannel(newChannel));
this.handleChangeChannel(newChannel);
socket.emit('new channel', newChannel);
this.setState({channelName: ''});
this.closeAddChannelModal(event);
}
}
validateChannelName() {
const { channels } = this.props;
if (channels.filter(channel => {
return channel.name === this.state.channelName.trim();
}).length > 0) {
return 'error';
}
return 'success';
}
openMoreChannelsModal(event) {
event.preventDefault();
this.setState({moreChannelsModal: true});
}
closeMoreChannelsModal(event) {
//event.preventDefault();
this.setState({moreChannelsModal: false});
}
createChannelWithinModal() {
this.closeMoreChannelsModal();
this.openAddChannelModal();
}
render() {
const { channels, messages } = this.props;
const filteredChannels = channels.slice(0, 8);
const moreChannelsBoolean = channels.length > 8;
const restOfTheChannels = channels.slice(8);
const newChannelModal = (
<div>
<Modal key={1} show={this.state.addChannelModal} onHide={::this.closeAddChannelModal}>
<Modal.Header closeButton>
<Modal.Title>Add New Channel</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={::this.handleModalSubmit} >
<Input
ref="channelName"
type="text"
help={this.validateChannelName() === 'error' && 'A channel with that name already exists!'}
bsStyle={this.validateChannelName()}
hasFeedback
name="channelName"
autoFocus="true"
placeholder="Enter the channel name"
value={this.state.channelName}
onChange={::this.handleModalChange}
/>
</form>
</Modal.Body>
<Modal.Footer>
<Button onClick={::this.closeAddChannelModal}>Cancel</Button>
<Button disabled={this.validateChannelName() === 'error' && 'true'} onClick={::this.handleModalSubmit} type="submit">
Create Channel
</Button>
</Modal.Footer>
</Modal>
</div>
);
const moreChannelsModal = (
<div style={{background: 'grey'}}>
<Modal key={2} show={this.state.moreChannelsModal} onHide={::this.closeMoreChannelsModal}>
<Modal.Header closeButton >
<Modal.Title>More Channels</Modal.Title>
<a onClick={::this.createChannelWithinModal} style={{'cursor': 'pointer', 'color': '#85BBE9'}}>
Create a channel
</a>
</Modal.Header>
<Modal.Body>
<ul style={{height: 'auto', margin: '0', overflowY: 'auto', padding: '0'}}>
{restOfTheChannels.map(channel =>
<ChannelListModalItem channel={channel} key={channel.id} onClick={::this.handleChangeChannel} />
)}
</ul>
</Modal.Body>
<Modal.Footer>
<button onClick={::this.closeMoreChannelsModal}>Cancel</button>
</Modal.Footer>
</Modal>
</div>
);
return (
<section>
<div>
<span style={{paddingLeft: '0.8em', fontSize: '1.5em'}}>
Channels
<button onClick={::this.openAddChannelModal} style={{fontSize: '0.8em', 'background': 'Transparent', marginLeft: '2.8em', 'backgroundRepeat': 'noRepeat', 'border': 'none', 'cursor': 'pointer', 'overflow': 'hidden', 'outline': 'none'}}>
<Glyphicon glyph="plus" />
</button>
</span>
</div>
{newChannelModal}
<div>
<ul style={{display: 'flex', flexDirection: 'column', listStyle: 'none', margin: '0', overflowY: 'auto', padding: '0'}}>
{filteredChannels.map(channel =>
<ChannelListItem style={{paddingLeft: '0.8em', background: '#2E6DA4', height: '0.7em'}} messageCount={messages.filter(msg => {
return msg.channelID === channel.name;
}).length} channel={channel} key={channel.id} onClick={::this.handleChangeChannel} />
)}
</ul>
{moreChannelsBoolean && <a onClick={::this.openMoreChannelsModal} style={{'cursor': 'pointer', 'color': '#85BBE9'}}> + {channels.length - 8} more...</a>}
{moreChannelsModal}
</div>
</section>
);
}
}