I have tried this every which way, but I can't get TestCafe to wait for the disabled attribute to be removed from an element.
This obviously blocks all further testing, since I need the button to be clickable before I can proceed in the flow.
fixture('create').page('locahost:3000');
test('one', async => {
const myIframe = Selector('#myIframe');
await t
.typeText('#input', 'words')
.click('#update')
.expect(myIframe.exists).ok('', { timeout: 10000 })
.switchToIframe(myIframe)
const activeStartButton = await Selector('#start').withAttribute('disabled');
await t
.expect(activeStartButton).notOk('', { timeout: 60000, allowUnawaitedPromise: true });
});
Regardless of whether I defined activeStartButton ahead of time, or add or remove await from the definition, put the selector directly in expect with or without await, separate this await block from the previous one or add it to the previous chain, TestCafe immediately throws an error atexpect(activeStartButton).notOk`
The error varies depending on my approach, but for this code:
AssertionError: start button remains disabled: expected [Function: __$$clientFunction$$] to be falsy"
Your code should look like this:
const selector = Selector('#start')
.with({visibilityCheck: true});
await t
.expect(selector.exists).ok({timeout: 10000}) // ensure the button is visible on the screen
.hover(selector) // access to the button via the mouse
.expect(selector.hasAttribute("disabled")).notOk({timeout: 10000}) // ensure the field is enabled
.click(selector);
Maybe you should also have a look to say goodbye to flakyness
This code:
const mySelector = Selector('any css selector');
await t
.expect(mySelector).notOk()
will always throw an error because the truthiness of mySelector is always true. So the above code is similar to this code:
assert(true).toBe(false).
Above mySelector is a promise object and the truthiness of a promise is always true.
Now if you write:
const mySelector = await Selector('any css selector');
await t
.expect(mySelector).notOk();
mySelector is a NodeSnaphsot object which is some sort of literal object with plenty of properties on it like:
{
textContent,
attributes,
id,
clientHeight,
...
}
The truthiness of a literal object is always true, and therefore the above
expect will still throw an error.
In fact, this problem could have been completely masked if the test code was instead:
const mySelector = await Selector('any css selector');
await t
.expect(mySelector).ok();
The above test code will always pass even if mySelector does not represent any existing element in the DOM.
Inside the expect you should assert only for a property or for a method of the Selector that returns a boolean value when using ok() or notOk().
Possible boolean properties are:
mySelector.hasChildElements
mySelector.hasChildNodes
mySelector.checked
mySelector.focused
mySelector.selected
mySelector.visible
mySelector.exists
Possible methods are:
mySelector.hasClass('className')
mySelector.hasAttribute('attributeName')
The `.withAttribute('attributeName') is just a filter method that returns a Selector object (i.e. a Promise) and the truthiness of this result is always true.
So when you are writing :
const mySelector = Selector('any css selector').withAttribute('attributeName');
it's more or less like writing this pseudo-code:
const mySelector = Selector('any css selector') // returns a collection of Selectors
.toArray() // convert it to an array
.filter((selector) => selector.hasAttribute('attributeName'))
.toPromise() // convert back to a promise object
Related
I am trying to implement infinite scroll using nuxt3.
Key target:
Initial load should contain server rendered content
So my code is
const { data, refresh } = await useAsyncData(async () => {
const response = await $fetch(`/api/whatever?limit=${limit}&skip=${skip}`);
return {
totalElements: response.count,
items: response.items,
};
});
Everything is fine here except I couldn't accumulate previous result with current result after refresh() to achieve infinite scroll because data is not accessible inside useAsyncData handler so i can't use
return {
totalElements: response.count,
items: [...data.value.items, ...response.items],
};
Also it is impossible to assign current value to ref and reuse it inside handler as server ref will take changes and client ref will be empty value at initial load.
I have a workaround but it is a kind of a crutch and sometimes results to a glitchy scroll behaviour because server rendered data.value.items is an array and after modifying data.value.items on client side it becomes an array of proxies.
// This code is outside useAsyncData composable
...
const response = await useFetch(...);
data.value.items = [...data.value.items, ...response.items];
Any ideas?
I have a component that renders a table of Inventoried computer equipment. Here is the relevant code for initial render:
let oEquiptByType = reactive({
Laptop: [],
iPad: [],
"Document Camera": [],
"Overhead Projector": [],
Chromebook: [],
Desktop: [],
MacBook: [],
Scanner: [],
});
// ======== Props =========== //
const props = defineProps({
propFormData: {},
});
// Now let's use Stein to retrieve the SS data
// eslint-disable-next-line no-unused-vars
const fetchSheetsData = function () {
const store = new SteinStore(
"https://api.steinhq.com/v1/storages/618e81028d29ba2379044caa"
);
store
.read("HS - Classrooms")
.then((data) => {
scrapDataHSClassrooms.value = data;
emptyRowsRemoved.value.forEach((item) => {
// Let's construct an object that separates equipment by type
// Check if property exists on oEquiptByType object
const exists = Object.prototype.hasOwnProperty.call(
oEquiptByType,
item["Equipment"]
);
// If item(row) is good lets push the row onto the corresponding Object Array
// in oEquiptByType. This will construct an object where each object property corresponds
// to an equipment category. And each oEquiptByType entry is an array where each array
// element is a row from the SS. e.g., oEquiptByType["Laptop"][3] is a row from
// SS and is a laptop.
if (exists) {
oEquiptByType[item["Equipment"]].push(item);
}
});
})
.catch((e) => {
console.error(e);
failure.value = true;
});
};
// =============== Called on component mount =============================== //
onMounted(fetchSheetsData);
The initial render is fine. Now I have a watcher on the prop so when someone submits a new item for the inventory I push that data onto the corresponding object array (ie, a laptop would be pushed onto the oEquiptByType[props.propFormData.Equipment] via oEquiptByType[props.propFormData.Equipment].push(props.propFormData);
// ================================================================ //
// ======================= Watch effects ========================== //
// ================================================================ //
watch(props.propFormData, () => {
// Push the submitted form item onto the reactive
// oEquiptByType object array. This update of Vue state
// will then be injected into DOM and automagically update browser display.
oEquiptByType[props.propFormData.Equipment].push(props.propFormData);
});
This works fine for the first item I add to backend as you can see here with original and then adding first item :
and after first item added (a laptop)
Notice the oEquiptByType[props.propFormData.Equipment] has the new item added. Great.
But now when I add a second item (a MacBook) is added this is resulting state:
Notice the Macbook array has been updated but also the Laptop array's last item has been overwritten with the Mac book entry??? And this behavior continues for any additional items added from a user. I have read docs over and do not see anything that would explain this behavior. I'm hoping maybe someone with more than my limited experience with Vue can help me out. Any additional info needed please let me know. Thanks...
Update:
Put a JSON.Stringify in watch function
Update two:
here is lineage of prop.FormData-
we start in form-modal and emit the form data like:
emit("emiterUIUpdate", formAsPlainObject);
then catch the data in the parent App.vue:
<FormModal
v-show="isModalVisible"
#close="closeModal"
#emiterUIUpdate="updateUI"
>
<DisplayScrap :propFormData="formData" />
const formData = reactive({});
// Method to be called when there is an emiterUIUpdate event emiited
// from form-modal.vue #param(data) is the form data sent from the
// form submission via the event bus. We will then send this data back
// down to child display-scrap component via a prop.
const updateUI = (data) => {
Object.assign(formData, data);
};
and then as posted previous in display-scrap.vue the prop propFormData is defined and watched for in the watch function. hope that helps..
It seems like the watch is getting triggered more often than you expect.
Might be that changes to props.propFormData are atomic and every incremental change triggers changes to the props, which in turn triggers the watch.
Try console logging the value of props.propFormData with JSON.stringify to see what changes are triggering it.
What happens here:
Your form modal emits the emiterUIUpdate event on Ok or Save (button)
Parent takes the object emitted and use Object.assing to copy all properties of emitted object to a formData reactive object. Instead of creating completely new object, you are just replacing the values of all properties of that object all and over again
The formData object is passed by a prop to child component and whenever it changes, it is pushed to target array
As a result, you have a multiple references to same object (formData hold by a parent component) and all those references are to same object in memory. Every Object.assign will overwrite properties of this object and all references will reflect those changes (because all references are pointing to the same object in memory)
Note that this has nothing to do with Vue reactivity - this is simple JavaScript - value vs reference
There is no clear answer to what to do. There are multiple options:
Simplest (and not clearest)
just do not use Object.assign - create new object every time "Save" is clicked
change formData to a ref - const formData = ref({})
replace the value of that ref on emiterUIUpdate event - formData.value = { ...data }
your watch handler in the child will stop working because you are watching props in a wrong way - instead of watch(props.propFormData, () => { use watch(() => props.propFormData, () => {
Better solution
the data should be owned by parent component
when modal emits new data (Save), Parent will just add the newly generated object into a list
share the data with DisplayScraps component using a prop (this can be a simple list or a computed creating object similar to oEquiptByType)
I need to load a dayjs locale dynamically on the client side. On the server, I can just require it and it works, but it will always lead to a hydration mismatch because there's no way on the client to wait until the
import(`dayjs/locale/${locale}.js`)
actually completes. Can I somehow tell next to wait for the import before beginning re-hydration on the client (since the server-rendered html is actually correct and rendered with the correct locale)?
I know it has been too long and probably you found the solution but here it is a custom hook I wrote which accepts a callback as an argument and will be called only once before rendering the component. and also returns whatever you return in the callback.
the code is simple. you can read and understand it or even improve it.
the code is right here. the gist page
the typescript code if the link gets broken in future:
const useComponentWillMount = <T>(cb: () => T): T => {
const isMountedRef = useRef(false)
const resultRef = useRef<T>()
if (!isMountedRef.current && typeof window !== "undefined") {
resultRef.current = cb()
}
isMountedRef.current = true
return resultRef.current
}
I have a few questions (I am new to Dart) w.r.t the code below which I found. What I am trying to achieve:
1) Write more than one line in the brackets for errors. But I get error saying 'set literals were supported upto 2.2....'. It would also be good to know the list of possible errors so I can alert the user accordingly.
2) Increment a counter pertaining to number of documents and keep storing document id's in one variable, by overwriting the last value if more than one document is found. I am trying to check that there should only be one document and take action if counter goes to 2.
void _checkIfAdmin() {
Firestore.instance
.collection('Admin')
.where('Email', isEqualTo: widget._user.email)
.snapshots()
.handleError((error)=>{
//
})
.listen((data) => data.documents.forEach((doc) => gpCounter+=1)); // Store document id?
print('Counter: $gpCounter');
}
Here I am getting printed gpCounter=0 when I know there are two documents in there. I tried adding async/await in the function but it seems the firebase statement does not return any future so I get a warning to use await only for futures.
I will try to address as many issues as I could understand in your question. If you have any other issues, you should probably ask another question.
Set literals: the syntax for anonymous functions is () {} and not () => {}.
() => is a shorthand syntax to return the value of a single expressions and in your case, you are returning a Set in your anonymous functions because {} create Set's (or Map's if you have colons in there).
The solution for this is (error) { ... }.
You print your gpCounter value synchronously, however, you are listening to a Stream.
Because of that, you should move your print statement to your listen function:
.listen(data) {
print('Counter: ${data.documents.length}');
})
I have no issues when using implicit updates (angelFire). However I need for some of my data use explicit updating. So I implemented angelFireCollection on the exact same ref I was using previously but despite the console.log explicitly saying that the read was granted and trying it with both with the onloadcallback and without, I don't get data directly into my assigned variable AND once the callback fires I get a strange looking object that DOES contain the data but not in the form I expect. My scope variable ends up with an empty collection. Never gets populated. Here is the code:
var streamController = function ($rootScope, $scope, $log, $location, angularFireCollection, profileService) {
//Wait for firebaseLogin...
$rootScope.$watch('firebaseAuth', init);
function init() {
if ($rootScope.firebaseAuth == false) {
return
};
var refUsers = new Firebase($rootScope.FBURL+'/users/'+$rootScope.uid);
$scope.profile = angularFireCollection(refUsers, function onload(snapshot) {
console.log(snapshot)
});
};
};
myApp.gwWebApp.controller('StreamController', ['$rootScope', '$scope', '$log', '$location', 'angularFireCollection', 'profileService',
streamController]);
}());
Here is what the console.log looks like ( ie; what snapshot looks like ):
>snapshot
T {z: R, bc: J, V: function, val: function, xd: function…}
Here is the earlier message before the snapshot was returned:
Firebase Login Succeeded! fbLoginController.js:16
FIREBASE: Attempt to read /users/529ccc5d1946a93656320b0a with auth={"username":"xxxxxxx#me.com","id":"529ccc5d1946a93656320b0a"} firebase.js:76
FIREBASE: /: "auth.username == 'admin'" firebase.js:76
FIREBASE: => false firebase.js:76
FIREBASE: /users firebase.js:76
FIREBASE: /users/529ccc5d1946a93656320b0a: "auth.id == $user" firebase.js:76
FIREBASE: => true firebase.js:76
FIREBASE:
FIREBASE: Read was allowed.
and finally the desired binding that ends up with an empty array: again from the console:
$scope.profile
[]
Anyone know what I could possibly be doing wrong?? This is like 5 lines of code. Frustrating.
I have put stops in angelFireCollection factory function and can see that the data is getting added to the collection in the callbacks inside that function but my binded variable never gets updated.
UPDATE
Ok experimenting with a plnkr. It seems that angularFireCollection EXPECTS your returning a LIST of items. The snapshot returns properly if you inspect snapshot.val() it will be whatever object structure was stored in firebase. IF you use angularFireCollection it does indeed bind to the variable HOWEVER it turns a non-list object into a garbled mess and you can not access the object user the normal dot operator. This is either a bug or it is a severe limitation of angularFireCollection which will cause me to revaluate how easily I can use firebase as the backend. I can't share my plnkr because it is accessing non-public data but tomorrow if i have time I will create a public firebase with an object store and demonstrate.
Ok. So it appears that indeed angularFireCollection is MEANT to be array based. Which is fine. It would be VERY helpful if the angularFire documentation was updated to make that clear. As such it is not an implicit vs explicit update technique.
For an explicit non-array based approach I have come up with the following code. Had I not been mislead by the documentation I would have gone down this path originally.
var MainCtrl = function($scope, angularFire) {
$scope.test = {};
var _url = 'https://golfwire.firebaseio.com/tmp';
var _ref = new Firebase(_url);
var promise = angularFire(_ref, $scope, 'implicit');
promise.then ( function(data){
$scope.explicit=angular.copy($scope.implicit );
});
}
You then work locally with the 'explicit' copy and when ready just update the 'implicit' by assigning: $scope.implicit = $scope.explicit.
Here is a plnkr: http://plnkr.co/edit/bLJrL1