Wordpress Gutenberg subscribe not getting updated meta field value - javascript

I've been trying to resolve this issue where using the Gutenberg meta box updates, doesn't fetch the new updated meta value.
Meta registration:
add_action('init', function() {
register_meta('post', 'open_unit', array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'auth_callback' => function() {
return current_user_can('edit_posts');
}
));
register_meta('post', 'open_active', array(
'type' => 'boolean',
'single' => true,
'show_in_rest' => true,
'auth_callback' => function() {
return current_user_can('edit_posts');
}
));
register_meta('post', 'open_abstract', array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'sanitize_callback' => function($text) {
return sanitize_text_field($text);
},
'auth_callback' => function() {
return current_user_can('edit_posts');
}
));
});
Enqueue assets:
add_action('enqueue_block_editor_assets', function() {
$screen = get_current_screen();
if ($screen->post_type === 'page') return;
wp_enqueue_script(
'open-panel',
plugin_dir_url( __FILE__ ) . 'js/admin.js',
array('wp-i18n', 'wp-blocks', 'wp-edit-post', 'wp-element', 'wp-editor', 'wp-components', 'wp-data', 'wp-plugins', 'wp-edit-post', 'wp-api-fetch'),
filemtime(dirname( __FILE__ ) . '/js/admin.js')
);
});
Javascript:
const el = element.createElement;
const { Fragment } = element;
const { registerPlugin } = plugins;
const { PluginDocumentSettingPanel } = editPost;
const { TextareaControl, ToggleControl, Text } = components;
const { withSelect, withDispatch, subscribe, registerStore } = data;
const ActiveCheckboxControl = compose.compose(
withDispatch(function(dispatch, props) {
return {
setMetaValue: function(metaValue) {
dispatch('core/editor').editPost(
//{ meta: { [props.metaKey]: (openValidate && metaValue) } }
{ meta: { [props.metaKey]: metaValue } }
);
}
}
}),
withSelect(function(select, props) {
return {
metaValue: select('core/editor').getEditedPostAttribute('meta')[props.metaKey],
}
}))(function(props) {
return el(ToggleControl, {
label: props.title,
checked: props.metaValue,
onChange: function(content) {
props.setMetaValue(content);
},
});
}
);
const AbstractTextControl = compose.compose(
withDispatch(function(dispatch, props) {
return {
setMetaValue: function(metaValue) {
dispatch('core/editor').editPost(
{ meta: { [props.metaKey]: metaValue } }
);
}
}
}),
withSelect(function(select, props) {
return {
metaValue: select('core/editor').getEditedPostAttribute('meta')[props.metaKey],
}
}))(function(props) {
return el(TextareaControl, {
label: props.title,
value: props.metaValue,
onChange: function(content) {
props.setMetaValue(content);
}
});
}
);
registerPlugin('open', {
render: function() {
return el(Fragment, {},
el(PluginDocumentSettingPanel,
{
name: 'open',
title: 'Open'
},
// Active
el(ActiveCheckboxControl,
{
metaKey: 'open_active',
title : 'Show'
}
),
// Abstract
el(AbstractTextControl,
{
metaKey: 'open_abstract',
title : 'Abstract'
}
)
)
);
}
});
...
let isSavingChecked = true;
let editor = data.select('core/editor');
const getOpenUnit = () => editor.getEditedPostAttribute('meta') ? editor.getEditedPostAttribute('meta').open_unit : null;
const getOpenActive = () => editor.getEditedPostAttribute('meta') ? editor.getEditedPostAttribute('meta').open_active : false;
const getOpenAbstract = () => editor.getEditedPostAttribute('meta') ? editor.getEditedPostAttribute('meta').open_abstract : null;
let openUnit = getOpenUnit();
let openActive = getOpenActive();
let openAbstract = getOpenAbstract();
console.log(openUnit);
const unsubscribe = subscribe( _.debounce( () => {
const isSavingPost = editor.isSavingPost();
const newOpenUnit = getOpenUnit();
const newOpenActive = getOpenActive();
const newOpenAbstract = getOpenActive();
if (isSavingPost) {
isSavingChecked = false;
} else {
if(!isSavingChecked) {
let post = editor.getCurrentPost();
let data = {
active: openActive ? 'active':'paused',
abstract: post.meta.open_abstract,
wp_id: post.id,
wp_title: post.title,
wp_url: post.link,
wp_image: post.featured_media
}
let openValidation = openValidate(data);
if (openValidation.valid) {
if(openActive !== newOpenActive || openAbstract !== newOpenAbstract || openUnit !== newOpenUnit) {
openRemote(data);
} else {
console.log(newOpenUnit); //This field is not returning the updated meta field from Wordpress
openRemoteUpdate(data);
}
} else {
wp.data.dispatch('core/notices').removeNotice('OPEN_NOTICE');
wp.data.dispatch('core/notices').createNotice(
'warning',
openValidation.messages.join(', '),
{ id: 'OPEN_NOTICE', isDismissible: true }
);
}
isSavingChecked = true;
openActive = newOpenActive;
openAbstract = newOpenAbstract;
openUnit = newOpenUnit;
}
}
}));
I am basically trying to fetch the updated meta field:
const getOpenUnit = () => editor.getEditedPostAttribute('meta') ? editor.getEditedPostAttribute('meta').open_unit : null;
But it's currently looking like this in the console where it's null(console.log(openUnit)) or empty(console.log(newOpenUnit)) https://share.getcloudapp.com/E0uYKWGv Lines 195 & 224
Any help or advice would be appreciated!

The easiest way I have found to get and set meta is using useEntityProp() in a function component. It is a lot easier to reason about than using withSelect and withDispatch.
import { __ } from '#wordpress/i18n';
import { useSelect } from '#wordpress/data';
import { useEntityProp } from '#wordpress/core-data';
import { TextControl } from '#wordpress/components';
export default function MetaComponent(props) {
const postType = useSelect((select) => {
return select('core/editor').getCurrentPostType();
});
const [meta, setMeta] = useEntityProp('postType', postType, 'meta');
return (
<TextControl
label={ __('Meta', 'pb') }
value={ meta.open_abstract ? meta.open_abstract : '' }
onChange={ (value) => setMeta({open_abstract: value}) }
/>
);
}

Related

Jest test function returning a function which is having window.location.href

How to write test case for below function ?
Expected is : If successPath is provided then onSignInSuccess should redirect it to successPath
export const onSignInSuccess = ( data ) => {
return ( ) => {
global.location.href = data?.detail?.data?.successPath;
}
}
What I tried so far is but its not working
const data = { detail : { data: { redirectPage: true, successPath: 'test.com' } } }
onSignInSuccess( data )()
expect( jest.fn() ).toHaveBeenCalledWith( 'test.com' )
I found the solution by mocking window.location.href
it( 'should redirect to successPath if given', ()=>{
global.window = Object.create( window );
const url = 'http://test.com';
Object.defineProperty( window, 'location', {
value: {
href: url
}
} );
const data = { detail : { data: { redirectPage: true, successPath: 'test.com' } } }
onSignInSuccess( data )()
expect( global.location.href ).toBe( 'test.com' )
} );

How to dispatch a Vue computed property

I´m trying to dispatch an object which is created in a computed.
I can´t get it to work as I´m fairly new to vue.js
I want to dispatch the object "updateObject" to the vuex-store.
Tried with setters but didn´t work. I think if I can set the "varia" object to the same object like "updateObject" then I could maybe dispatch it?
Hope somebody can help me.
Here is my code:
<template>
<div class="detail">
<b-row align-v="center"><b-button variant="success" #click="submit()">submit</b-button></b-row>
// some more code...
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
subID: '',
res: '',
showAlert: true,
varia: null
}
},
computed: {
...mapState([
'FA',
'Main',
'Sub',
'layouttype'
]),
getVariable: function (Sub, layouttype) {
const subID = this.layouttype.sub_id
var filterObj = this.Sub.filter(function (e) {
return e.sub_id === subID
})
console.log(filterObj)
return filterObj
},
updateObject: {
// getterfunction
get: function () {
var len = this.getVariable.length
var res = []
for (var i = 0; i < len; i++) {
if (i in this.getVariable) {
var val = this.getVariable[i].variable
res.push(val)
}
}
console.log(res)
var ergebnis = {}
res.forEach(key => {
if (this.FA[key]) {
ergebnis[key] = this.FA[key]
}
})
return ergebnis
},
// setterfunction
set: function (value) {
this.varia = value
}
}
},
methods: {
submit () {
this.$store.dispatch('sendData', this.ergebnis)
}
}
}
</script>
It tell´s me "this.ergebnis" is undefined
You can try it declaring "ergebnis" as global variable under data as
export default {
data () {
return {
subID: '',
res: '',
showAlert: true,
varia: null,
ergebnis : {}
}
},
computed: {
...mapState([
'FA',
'Main',
'Sub',
'layouttype'
]),
getVariable: function (Sub, layouttype) {
const subID = this.layouttype.sub_id
var filterObj = this.Sub.filter(function (e) {
return e.sub_id === subID
})
console.log(filterObj)
return filterObj
},
updateObject: {
// getterfunction
get: function () {
var len = this.getVariable.length
var res = []
for (var i = 0; i < len; i++) {
if (i in this.getVariable) {
var val = this.getVariable[i].variable
res.push(val)
}
}
console.log(res)
res.forEach(key => {
if (this.FA[key]) {
this.ergebnis[key] = this.FA[key]
}
})
return this.ergebnis
},
// setterfunction
set: function (value) {
this.varia = value
}
}
},
methods: {
submit () {
this.$store.dispatch('sendData', this.ergebnis)
}
}
}
Now ergebnis is accessible

Return single property from object array

When I try to return a specific property from my array, the terminal say:
TypeError: Cannot read property 'url' of undefined.
I want to search a specific element that valid this button.id === this.state.currentId control. So, when it's true I want to return element.buttonColor and, in the other method I want that its return 'element.url'. Why the first method: getCurrentBackgroundColor work and the second: getCurrentImage don't work?
this.state = {
currentId: null,
buttons: [
{
id: 0,
url: "./url1.jpg",
buttonColor: "#b2d8ca"
},
{
id: 1,
url: "./url2.jpg",
buttonColor: "#fef6bb"
}
],
};
getCurrentBackgroundColor = () => {
const currentButton = this.state.buttons.find((button) => {
return button.id === this.state.currentId;
});
return currentButton === undefined ? "" : currentButton.buttonColor;
};
getCurrentImage = () => {
const currentButton = this.state.buttons.find((button) => {
return button.id === this.state.currentId;
});
return currentButton === undefined ? "" : currentButton.url;
};
render() {
return (
<div className="App">
<LeftRight
backColor={this.getCurrentBackgroundColor()}
image={this.getCurrentImage()}
/>
</div>
}
//In the LEFTRIGHT class
<div
id="left"
style={{ backgroundColor: this.props.backColor }}
key={this.props.backColor}
>
<img src={this.props.image}/>
</div>
I noticed your current id is not set, you can change 'null' in the return sentence.
this.state = {
currentId: 0,
buttons: [
{
id: 0,
url: "./url1.jpg",
buttonColor: "#b2d8ca"
},
{
id: 1,
url: "./url2.jpg",
buttonColor: "#fef6bb"
}
],
};
getCurrentBackgroundColor = () => {
const currentButton = this.state.buttons.find((button) => {
return button.id === this.state.currentId;
});
return currentButton === undefined ? "" : currentButton.buttonColor;
};
getCurrentImage = () => {
const currentButton = this.state.buttons.find((button) => {
return button.id === this.state.currentId;
});
return currentButton === undefined ? "" : currentButton.url;
};
console.log("Background: ", getCurrentBackgroundColor())
console.log("Current Image: ", getCurrentImage())
this.state = {
currentId: 1,
buttons: [{
id: 0,
url: "./url1.jpg",
buttonColor: "#b2d8ca"
},
{
id: 1,
url: "./url2.jpg",
buttonColor: "#fef6bb"
}
],
};
getCurrentBackgroundColor = () => {
this.state.buttons.map((button) => {
if (this.state.currentId === button.id) return console.log(button.buttonColor)
});
};
getCurrentBackgroundColor()

Yields "TypeError: Cannot read property 'xxxx' of undefined" after running jest with Vue

I'm trying to make a test using jest with Vue.
the details below.
Problem:
Can't mount using shallowMount option.
Situation:
Run the test after mounting the component using shallowMount option that provides in Vue-test-utils.
Throw error "Cannot read property 'XXXX' of undefined
This is my test code.
import myComponent from '#/~';
import Vuex from 'vuex';
import Vuelidate from 'vuelidate';
import { shallowMount, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(Vuelidate);
describe('myComponent~', () => {
let store;
beforeEach(() => {
store = new Vuex.Store({
modules: {
user: {
namespaced: true,
getters: {
profile: () => {
const profile = { name: 'blahblah' };
return profile;
},
},
},
},
});
});
describe('profile.name is "blahblah"', () => {
it('return something~', () => {
const wrapper = shallowMount(myComponent, {
localVue,
store,
mocks: {
$api: {
options: {
testMethod() {
return new Promise((resolve, reject) => {
resolve('test');
});
},
},
},
$i18n: {
t() {
return {
EN: 'EN',
KO: 'KO',
JP: 'JA',
SC: 'zh-CN',
TC: 'tw-CN',
};
},
},
},
});
expect(wrapper.find('.profile').text()).toBe('blahblah');
});
I think the problem is that property isn't set as a specified value or an empty value like an array or object.
But I don't know how I set properly the properties in my logic.
For example,
when the error yields "Cannot read property 'images' of undefined",
I add to a wrapper in the relevant method like this.
exampleMethod() {
this.something = this.something.map(item => {
if (item.detailContent.images) { // <-- the added wrapper is here
~~~logic~~~~
}
})
}
But the undefined properties are so many, I also think this way is not proper.
How I do solve this problem?
added
These are details about the above example method:
exampleMethod() {
this.something = this.something.map(item => {
let passValidation = false;
let failValidation = false;
if (item.detailContent.images) {
if (this.detail.showLanguages.includes(item.code)) {
if (this.configId !== 'OPTION1') {
item.detailContent.images = item.detailContent.images.map(element => {
return {
...element,
required: true,
}
});
}
checkValidationPass = true;
} else {
if (this.configId !== 'OPTION1') {
item.detailContent.images = item.detailContent.images.map(element => {
return {
...element,
required: false,
}
});
}
checkValidationPass = false;
}
return {
...item,
required: passValidation,
warning: failValidation,
}
}
});
if (this.configId === 'OPTION2') {
this.checkOption2Validation();
} else if (this.configId === 'OPTION3') {
this.checkOption3Validation();
} else {
this.checkOption1Validation();
}
},
And this is 'this.something':
data() {
return {
something: []
}
}
The detailContent is set here.
setMethod() {
this.something = [
...this.otherthings,
];
this.something = this.something.map(item => {
let details1 = {};
if (this.configId === 'OPTION2') {
details1 = {
images: [
{ deviceType: 'PC', titleList: [null, null], imageType: 'IMAGE' },
{ deviceType: 'MOBILE', titleList: [null, null, null] }
]
};
} else if (this.configId === 'OPTION3') {
details1 = {
images: [
{ deviceType: 'PC' },
{ deviceType: 'MOBILE' }
],
links: { linkType: 'EMPTY' },
};
}
let details2 = {
mainTitle: {
content: null,
}
}
let checkValidation = false;
this.detail.detailLanguages.forEach(element => {
if (element.language === item.code) {
details1 = { ...element };
if (!!element.mainTitle) {
details2 = { ...element };
} else {
details2 = {
...details2,
...element
};
}
if (this.configId !== 'OPTION1') {
details1.images = details1.images.map(image => {
return {
...image,
required: true,
}
});
}
checkValidation = true;
}
});
return {
...item,
detailContent: this.configId !== 'OPTION1' ? details1 : details2,
required: false,
warning: false,
}
});
},

GraphQL Validation Error on mutation

I am trying to set up a mutation with modified code from the Relay Todo example.
When I try to compile I get the following error:
-- GraphQL Validation Error -- AddCampaignMutation --
File: /Users/me/docker/relay/examples/todo/js/mutations/AddCampaignMutation.js
Error: Cannot query field "addCampaign" on type "Mutation".
Source:
>
> mutation AddCampaignMutation {addCampaign}
> ^^^
-- GraphQL Validation Error -- AddCampaignMutation --
File: /Users/me/docker/relay/examples/todo/js/mutations/AddCampaignMutation.js
Error: Unknown type "AddCampaignPayload". Did you mean "AddTodoPayload" or "RenameTodoPayload"?
Source:
>
> fragment AddCampaignMutationRelayQL on AddCampaignPayload #relay(pattern: true) {
> ^^
I have duplicated the Todo code so I don't know why the Todo mutation is working correctly but my new Campaign test isn't.
This is my database.js file, I have removed the Todo related items to make the document easier to read:
export class Campaign {}
export class User {}
// Mock authenticated ID
const VIEWER_ID = 'me';
// Mock user data
const viewer = new User();
viewer.id = VIEWER_ID;
const usersById = {
[VIEWER_ID]: viewer,
};
// Mock campaign data
const campaignsById = {};
const campaignIdsByUser = {
[VIEWER_ID]: [],
};
let nextCampaignId = 0;
addCampaign('Campaign1');
addCampaign('Campaign2');
addCampaign('Campaign3');
addCampaign('Campaign4');
export function addCampaign(text) {
const campaign = new Campaign();
//campaign.complete = !!complete;
campaign.id = `${nextCampaignId++}`;
campaign.text = text;
campaignsById[campaign.id] = campaign;
campaignIdsByUser[VIEWER_ID].push(campaign.id);
return campaign.id;
}
export function getCampaign(id) {
return campaignsById[id];
}
export function getCampaigns(status = 'any') {
const campaigns = campaignIdsByUser[VIEWER_ID].map(id => campaignsById[id]);
if (status === 'any') {
return campaigns;
}
return campaigns.filter(campaign => campaign.complete === (status === 'completed'));
}
This is my schema.js file, again I have removed the Todo related items to make the document easier to read:
import {
GraphQLBoolean,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
} from 'graphql';
import {
connectionArgs,
connectionDefinitions,
connectionFromArray,
cursorForObjectInConnection,
fromGlobalId,
globalIdField,
mutationWithClientMutationId,
nodeDefinitions,
toGlobalId,
} from 'graphql-relay';
import {
Campaign,
addCampaign,
getCampaign,
getCampaigns,
User,
getViewer,
} from './database';
const {nodeInterface, nodeField} = nodeDefinitions(
(globalId) => {
const {type, id} = fromGlobalId(globalId);
if (type === 'User') {
return getUser(id);
} else if (type === 'Campaign') {
return getCampaign(id);
}
return null;
},
(obj) => {
if (obj instanceof User) {
return GraphQLUser;
} else if (obj instanceof Campaign) {
return GraphQLCampaign;
}
return null;
}
);
/**
* Define your own connection types here
*/
const GraphQLAddCampaignMutation = mutationWithClientMutationId({
name: 'AddCampaign',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
campaignEdge: {
type: GraphQLCampaignEdge,
resolve: ({localCampaignId}) => {
const campaign = getCampaign(localCampaignId);
return {
cursor: cursorForObjectInConnection(getCampaigns(), campaign),
node: campaign,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({text}) => {
const localCampaignId = addCampaign(text);
return {localCampaignId};
},
});
const GraphQLCampaign = new GraphQLObjectType({
name: 'Campaign',
description: 'Campaign integrated in our starter kit',
fields: () => ({
id: globalIdField('Campaign'),
text: {
type: GraphQLString,
description: 'Name of the campaign',
resolve: (obj) => obj.text,
}
}),
interfaces: [nodeInterface]
});
const {
connectionType: CampaignsConnection,
edgeType: GraphQLCampaignEdge,
} = connectionDefinitions({
name: 'Campaign',
nodeType: GraphQLCampaign,
});
const GraphQLUser = new GraphQLObjectType({
name: 'User',
fields: {
id: globalIdField('User'),
campaigns: {
type: CampaignsConnection,
args: {
...connectionArgs,
},
resolve: (obj, {...args}) =>
connectionFromArray(getCampaigns(), args),
},
totalCount: {
type: GraphQLInt,
resolve: () => getTodos().length,
},
completedCount: {
type: GraphQLInt,
resolve: () => getTodos('completed').length,
},
},
interfaces: [nodeInterface],
});
const Root = new GraphQLObjectType({
name: 'Root',
fields: {
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
node: nodeField,
},
});
This is my AddCampaignMutation.js file:
import Relay from 'react-relay';
export default class AddCampaignMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id,
totalCount,
}
`,
};
getMutation() {
console.log('getMutation');
return Relay.QL`mutation{addCampaign}`;
}
getFatQuery() {
console.log('getFatQuery');
return Relay.QL`
fragment on AddCampaignPayload #relay(pattern: true) {
campaignEdge,
viewer {
campaigns,
},
}
`;
}
getConfigs() {
console.log('getConfigs');
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'campaigns',
edgeName: 'campaignEdge',
rangeBehaviors: ({orderby}) => {
if (orderby === 'newest') {
return 'prepend';
} else {
return 'append';
}
},
//rangeBehaviors: ({status}) => {
// if (status === 'completed') {
// return 'ignore';
// } else {
// return 'append';
// }
//},
}];
}
getVariables() {
console.log('getVariables');
return {
text: this.props.text,
};
}
getOptimisticResponse() {
console.log('getOptimisticResponse');
return {
// FIXME: totalCount gets updated optimistically, but this edge does not
// get added until the server responds
campaignEdge: {
node: {
text: this.props.text,
},
},
viewer: {
id: this.props.viewer.id,
totalCount: this.props.viewer.totalCount + 1,
},
};
}
}
And finally this is the app file that contains my text input and the call to AddCampaignMutation:
import AddTodoMutation from '../mutations/AddTodoMutation';
import AddCampaignMutation from '../mutations/AddCampaignMutation';
import TodoListFooter from './TodoListFooter';
import TodoTextInput from './TodoTextInput';
import React from 'react';
import Relay from 'react-relay';
class TodoApp extends React.Component {
_handleTextInputSave = (text) => {
debugger;
this.props.relay.commitUpdate(
new AddTodoMutation({text, viewer: this.props.viewer})
);
};
_campaignHandleTextInputSave = (text) => {
debugger;
this.props.relay.commitUpdate(
new AddCampaignMutation({text, viewer: this.props.viewer})
);
};
render() {
const hasTodos = this.props.viewer.totalCount > 0;
return (
<div>
<section className="todoapp">
<header className="header">
<TodoTextInput
autoFocus={true}
className="new-campaign"
onSave={this._campaignHandleTextInputSave}
placeholder="Campaign name"
/>
<h1>
todos
</h1>
<TodoTextInput
autoFocus={true}
className="new-todo"
onSave={this._handleTextInputSave}
placeholder="What needs to be done?"
/>
</header>
{this.props.children}
{hasTodos &&
<TodoListFooter
todos={this.props.viewer.todos}
viewer={this.props.viewer}
/>
}
</section>
</div>
);
}
}
export default Relay.createContainer(TodoApp, {
fragments: {
viewer: () => Relay.QL`
fragment on User {
totalCount,
${AddTodoMutation.getFragment('viewer')},
${AddCampaignMutation.getFragment('viewer')},
${TodoListFooter.getFragment('viewer')},
}
`,
},
});
Well I feel a bit silly but I found out what the problem was. I didn't realise that my schema.json file was not updating!
If anyone has a similar problem make sure that the schema.json file is up to date by running the following command to rebuild it:
npm run-script update-schema

Categories

Resources