Testing Javascript for your Gutenberg Integrated Plugin

(Edit October 10, 2018) Note: This article is now out of date but is kept published for reference purposes. Nearly everything in Gutenberg is published as a package so for the purpose of testing you can include those packages as a devDependency in your package.json file and jest will know to reference those in tests. 

As a part of my work with Event Espresso I’ve been assisting them with moving over to a more modern Javascript build system.  This has been prompted by the upcoming new editor for WordPress codenamed Gutenberg.  I’m not going to spend a whole lot of time talking about Gutenberg since there’s already a ton of information on the internet.  What I want to do in this post is outline a way for plugin developers to test their custom components/blocks built for Gutenberg using the Jest framework.

Writing javascript tests using Jest is pretty straightforward and this isn’t a how-to-post since there’s a lot of that already available (including this readme the Gutenberg team prepared).  Instead I thought it’d be useful to write how I solved a problem with a particular set of tests I was trying to write for some code I had written (especially since I couldn’t find anything useful on the internet myself to help with it).

The Problem

For Event Espresso I’m creating a reusable react component that exposes a Event Selector populated with events currently on the user’s site.  This will end up getting used in various parts of the Gutenberg friendly api we’re building.  I wanted a way to test this component using jest but ran into a few problems:

  1. The EventSelect component is composed of the Placeholder, SelectControl, and Spinner components from @wordpress/components (or  wp.elements ).
  2. There’s a few other dependencies in file for this component including @wordpress/i18n and @wordpress/data
  3. Gutenberg itself is a plugin and most of the dependencies the code has on Gutenberg itself are thus not available in the context of the javascript being tested (because of assumption of Gutenberg functionality being exposed on the wp global at runtime).
  4. I want to at a minimum use jest’s snapshot feature (using shallow) to verify that EventSelect is rendered correctly depending on the various props provided to it. 

While I could mock all of the dependencies, mocking the WordPress components being used would mean needing to make the assumption they would never change.

The Solution

I’ll get into the specifics of how I fixed this first, and then I’ll take some time to explain why it works (or in some cases why I think it works because frankly I’m still learning Jest and its intricacies).

The very first thing that needs done is importing gutenberg as a package.  Wait what?  Gutenberg is an npm package?  It’s not published to npm, however technically, anything with a npm package.json file in it is already a package.  If it’s hosted on github, you can install it.  So the first step to exposing gutenberg javascript for our tests is to install it!  How do you do that?

npm install WordPress/gutenberg#master --save-dev

That’s it!  In case you’re wondering, npm automatically looks on github for packages if they aren’t in the npm package repository.  So the format I used corresponds to the github {organization}/{repo}#{branch} format.

Next there’s some configuration file changes needed.

The jest.config.json file ended up with this.

{
	"rootDir": "../../../",
	"collectCoverageFrom": [
		"assets/src/**/*.js",
		"!**/node_modules/**",
		"!**/vendor/**",
		"!**/test/**"
	],
	"moduleDirectories": ["node_modules"],
	"moduleNameMapper": {
		"@eventespresso\\/(eejs)": "assets/src/$1",
		"@wordpress\\/(blocks|components|date|editor|element|data|utils|edit-post|viewport|plugins|core-data)": "<rootDir>/node_modules/gutenberg/$1",
		"tinymce": "<rootDir>/tests/javascript-config/unit/mocks/tinymce",
		"@wordpress/i18n": "<rootDir>/tests/javascript-config/unit/mocks/i18n"
	},
	"setupFiles": [
		"core-js/fn/symbol/async-iterator",
		"<rootDir>/tests/javascript-config/unit/setup-globals"
	],
	"preset": "@wordpress/jest-preset-default",
	"testPathIgnorePatterns": [
		"/node_modules/",
		"/test/e2e"
	],
	"transformIgnorePatterns": [
		"node_modules/(?!(gutenberg)/)"
	]
}

Three particular  properties you should take note of are:

moduleNameMapper

The property allows you to map module names to a specific location or to a mock.  In this case I wanted to map all @wordpress/* packages to the gutenberg package I just installed.  So this line does exactly that:

"@wordpress\\/(blocks|components|date|editor|element|data|utils|edit-post|viewport|plugins|core-data)": "<rootDir>/node_modules/gutenberg/$1",

Wait a minute!  How can you be using things like@wordpress/data for your imports if that isn’t a package?

Most Gutenberg tutorials you see on the internet give instructions for using the wp global as an external in your js source files, resulting in something like this:

const { Placeholder } = wp.components;
const { withSelect } = wp.data;

This works just fine,  however, if Gutenberg ever publishes some of these things as a bona-fide package, and you want to use that package, you’d have to go through and change every file in your source to to imports. I’m lazy, I don’t want to have to go through all my files if that happens. So instead I want to do this:

import { Placeholder } from '@wordpress/components';
import { withSelect } from '@wordpress/data';

However, if I do that, Webpack won’t know what to do with those imports because @wordpress/components and @wordpress/data don’t exist in node_modules.  So we need to help webpack out.  In the webpack configuration file I have something like this:

const externals = {
	'@wordpress/data': {
		this: [ 'wp', 'data' ],
	},
	'@wordpress/components': {
		this: [ 'wp', 'components' ],
	},
};

The externals object is then assigned to the externals configuration property for webpack.  What this does is alias@wordpress/data to the wp.data global.  So on builds any imports are correctly translated to use the wp.data global.

However, when running jest tests, jest does not work with the webpack builds (obviously) and is oblivious to this aliasing.  So when it encounters the imports (and goes to translate them to commonjs modules) it can’t find the @wordpress/* packages.  We get around that by using the moduleNameMapper property.

You’ll also notice that I mapped tinymce to a specific location for a mock.   What I discovered is that jest will follow the package import chain all the way down.  I would hit import not found for tinymce because guess what?  Gutenberg itself sets up tinymce as an external in its webpack config.  Since I’ll never need tinymce for my own testing (and shouldn’t need it anyways), I can just mock it.  So this line ends up being:

"tinymce": "<rootDir>/tests/javascript-config/unit/mocks/tinymce",

The path it points to simply contains this the tinymce.js file I created with this as its contents:

module.exports = jest.fn()

Which is just a handy method jest provides for mocking.

Finally, you’ll see that I also mock @wordpress/i18n.  The interesting thing here is that because @wordpress/i18n exists as a package.  I didn’t have to mock it.  However, I don’t really need to initialize jed or anything with i18n so to avoid errors related to it not being initialized in the test environment (and the usage of an actual text_domain in any i18n function calls), its better to just mock it.  So this line…

"@wordpress/i18n": "<rootDir>/tests/javascript-config/unit/mocks/i18n"

…points to the i18n.js file which currently has this as its contents:

module.exports = {
	 __ : (string) => {
		return string;
	}
};

Right now I’m only mocking the __ function, but as I start testing various code that utilizes other wp.i18n functions I’ll add them to this as well.

The next property I want to zero in on in the jest.config.json file is the setupFiles property.  This property is used to indicate any files you want executed before jest runs tests.  In particular, take note of this file reference:

"<rootDir>/tests/javascript-config/unit/setup-globals"

This points to a setup-globals.js file at that path and its contents are this:

/**
 * Setup globals used in various tests
 */
// Setup eejsdata global. This is something set in EE core via
// wp_localize_script so its outside of the build process.
global.eejsdata = {
	data: {
		testData: true
	},
};

// Set up `wp.*` aliases.  Doing this because any tests importing wp stuff will
// likely run into this.
global.wp = {
	shortcode: {
		next() {},
		regexp: jest.fn().mockReturnValue( new RegExp() ),
	},
};

[
	'element',
	'components',
	'utils',
	'blocks',
	'date',
	'editor',
	'data',
	'core-data',
	'edit-post',
	'viewport',
	'plugins',
].forEach( entryPointName => {
	Object.defineProperty( global.wp, entryPointName, {
		get: () => require( 'gutenberg/' + entryPointName ),
	} );
} );

This is needed not because I’m going to be using the wp global anywhere in the source js I’m testing, but because I’m actually importing Gutenberg source for the tests, some of the Gutenberg files ARE referencing the wp globals and thus they need to be defined to avoid undefined errors when running tests.  With this bit of code, we’re linking up the wp.{module name} with its module location in the node_modules folder.  So for instance anytime jest encounters wp.blocks it will set the property to the module loaded from node_modules/gutenberg/blocks.  

The final property I want to zero in on for the jest.config.json file is the transformIgnorePatterns property.  For a while I was struggling with the following error every time I ran a test (prior to this property value being inserted):

huh? what do you mean unexpected token import!

Jest expects packages that are included to be pre-compiled (i.e from ES6 to commonjs).  So if you have any imports that point to uncompiled packages you’ll get the above kind of error.  Since we’re including gutenberg as a package straight from its repo, we don’t have the compiled assets, hence the need to let jest know it needs to transform any imports it comes across in gutenberg. I accidentally discovered this when I was reading through the Jest documentation and came across this line:

Since all files inside node_modules are not transformed by default, Jest will not understand the code in these modules, resulting in syntax errors.

Jest Documentation for transformIgnorePatterns

Oooooo, ya I’m getting syntax errors.  What I didn’t realize is that this property allows you to whitelist things you want to be transformed. Groovy, so as a reminder here’s what I ended up using:

"transformIgnorePatterns": [
		"node_modules/(?!(gutenberg)/)"
	]

This then made sure that anytime the node_modules/gutenberg package (or any dependencies in those packages) were imported, the code referenced would be transformed into something jest can work with.  Say good bye to syntax errors!

Winner winner, chicken dinner!

With all that work, the code I wanted tested:

/**
 * External dependencies
 */
import { stringify } from 'querystringify';
import moment from 'moment';
import { isUndefined, pickBy, isEmpty } from 'lodash';
import PropTypes from 'prop-types';

/**
 * WP dependencies
 */
import { Placeholder, SelectControl, Spinner } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { withSelect } from '@wordpress/data';

/**
 * Internal dependencies
 */
import { buildEventOptions } from './build-event-options';

const nowDateAndTime = moment();

export const EventSelect = ( {
	events,
	onEventSelect,
	selectLabel,
	selectedEventId,
	isLoading,
} ) => {
	if ( isLoading || isEmpty( events ) ) {
		return <Placeholder key="placeholder"
			icon="calendar"
			label={ __( 'EventSelect', 'event_espresso' ) }
		>
			{ isLoading ?
				<Spinner /> :
				__(
					'There are no events to select from. You need to create an event first.',
					'event_espresso'
				)
			}
		</Placeholder>;
	}

	return <SelectControl
		label={ selectLabel }
		value={ selectedEventId }
		options={ buildEventOptions( events ) }
		onChange={ ( value ) => onEventSelect( value ) }
	/>;
}

/**
 * @todo some of these proptypes are likely reusable in various place so we may
 * want to consider extracting them into a separate file/object that can be
 * included as needed.
 * @type {{events: *, onEventSelect, selectLabel: *, selectedEventId: *,
 *   isLoading: *, attributes: {limit: *, orderBy: *, order: *, showExpired: *,
 *   categorySlug: *, month: *}}}
 */
EventSelect.propTypes = {
	events: PropTypes.arrayOf( PropTypes.shape( {
		EVT_name: PropTypes.string.required,
		EVT_ID: PropTypes.number.required,
	} ) ),
	onEventSelect: PropTypes.func,
	selectLabel: PropTypes.string,
	selectedEventId: PropTypes.number,
	isLoading: PropTypes.bool,
	attributes: PropTypes.shape( {
		limit: PropTypes.number,
		orderBy: PropTypes.oneOf( [
			'EVT_name',
			'EVT_ID',
			'start_date',
			'end_date',
			'ticket_start',
			'ticket_end',
		] ),
		order: PropTypes.oneOf( [ 'asc', 'desc' ] ),
		showExpired: PropTypes.bool,
		categorySlug: PropTypes.string,
		month: PropTypes.month,
	} ),
};

EventSelect.defaultProps = {
	attributes: {
		limit: 20,
		orderBy: 'start_date',
		order: 'desc',
		showExpired: false,
	},
	selectLabel: __( 'Select Event', 'event_espresso' ),
	isLoading: true,
	events: [],
};

/**
 * Used to map an orderBy string to the actual value used in a REST query from
 * the context of an event.
 * @todo this should be moved to a mapper library for various EE Rest Related
 * things maybe?
 *
 * @param {string} orderBy
 *
 * @return { string } Returns an actual orderBy string for the REST query for
 * 					  the provided alias
 */
const mapOrderBy = ( orderBy ) => {
	const orderByMap = {
		start_date: 'Datetime.DTT_EVT_start',
		end_date: 'Datetime.DTT_EVT_end',
		ticket_start: 'Datetime.Ticket.TKT_start_date',
		ticket_end: 'Datetime.Ticket.TKT_end_date',
	};
	return isUndefined( orderByMap[ orderBy ] ) ? orderBy : orderByMap[ orderBy ];
};

const whereConditions = ( { showExpired, categorySlug, month } ) => {
	const where = [];
	const GREATER_AND_EQUAL = encodeURIComponent( '>=' );
	const LESS_AND_EQUAL = encodeURIComponent( '<=' );

	if ( ! showExpired ) {
		where.push( 'where[Datetime.DTT_EVT_end**expired][]=>&where[Datetime.DTT_EVT_end**expired][]=' +
			nowDateAndTime.local().format() );
	}
	if ( categorySlug ) {
		where.push( 'where[Term_Relationship.Term_Taxonomy.Term.slug]=' + categorySlug );
	}
	if ( month && month !== 'none' ) {
		where.push( 'where[Datetime.DTT_EVT_start][]=' + GREATER_AND_EQUAL + '&where[Datetime.DTT_EVT_start][]=' +
			moment().month( month ).startOf( 'month' ).local().format() );
		where.push( 'where[Datetime.DTT_EVT_end][]=' + LESS_AND_EQUAL + '&where[Datetime.DTT_EVT_end][]=' +
			moment().month( month ).endOf( 'month' ).local().format() );
	}
	return where.join( '&' );
};

export default withSelect( ( select, ownProps ) => {
	const { limit, order, orderBy } = ownProps.attributes;
	const where = whereConditions( ownProps.attributes );
	const { getEvents, isRequestingEvents } = select( 'eventespresso/lists' );
	const queryArgs = {
		limit,
		order,
		order_by: mapOrderBy( orderBy ),
	};
	let queryString = stringify( pickBy( queryArgs,
		value => ! isUndefined( value ),
	) );

	if ( where ) {
		queryString += '&' + where;
	}
	return {
		events: getEvents( queryString ),
		isLoading: isRequestingEvents( queryString ),
	};
} )( EventSelect );

And the actual test I tried to get working:

import { shallow } from 'enzyme';
import { EventSelect } from '../index';

describe( 'EventSelect', () => {
	it( 'should render', () => {
		const wrapper = shallow( <EventSelect /> );
		expect( wrapper ).toMatchSnapshot();
	} );
} );

results in a winner!

whoah, there’s that green I’m looking for!

I’m not done yet, but the big part is done, now I can go ahead and write tests for various states of the component.

Any thoughts?  I’m pretty new to jest so there may be some things I’m not doing “right”.  If you’re a javascript/jest guru and have some pointers to help improve on this I’m all ears 🙂

If you want to follow along with my journey in writing this component and the related tests etc, you can go to this pull request.

FireHost and WordPress Multi-site… how well do they play together?

Recently, one of my clients purchased a server with FireHost.com.  We’d been on the search for a new web host for some time now to serve as the infrastructure supporting upcoming web applications we have in the works.  We needed a company who is well recommended, and will help us scale and scale quickly.

You pay a more for a host like this  but it’s part of the investment costs you need to make if you want to be positioned well for solid growth as a business.

Anyways, the purpose of this post is NOT to discredit or gripe about yet another hosting company that fails to live up to expectations. We actually really like the setup we have at FireHost. Although getting things setup were a bit of a pain – their support has been very prompt and generally okay.  No, this post is more of a fyi for folks who are in a similar situation as us.  I couldn’t find any information on this subject on the nets so thought I’d post my own findings.

One of the major components of some projects we are working on this year involves the use of WordPress multisite.  It will provide the backbone of what we are building and is crucial that we have a server environment that supports this.  FireHost does except for one niggling problem.  They have a super awesome “Web Application Protection” firewall that works really well, too well, and prevents normal usage of WordPress multisite.

At issue is that any subsites created on WordPress multisite will fully function as long as those subsites don’t post any images or certain html in their posts.  Cause if they do,  BAM, the firewall sees that as a xss attack and shuts her down.

That’s no good is it?  No.  But wait, FireHost has the solution.  All you have to do, is whenever this happens you just send them the path for the sites that the firewall does its thing on and they’ll add an exception.  Greeatt!  Except that we’re planning on using WordPress multisite to well actually make it easy for people to signup and get started on a new site right away (you know kind of like how people expect things to work? right?).  So yeah, major pain to have to send a block of paths every time the firewall acts up.

I’m not going to tell you the solution we worked out but let’s just say not ideal.

Silly?  Yeah.  I get it. But obviously someone at FireHost needs to do some thinking about how this firewall is setup and put something in place to allow for easier management of WordPress multisite while keeping the firewall working on things that it should work on, or at least be clear about the side affect of the WAP for those using WP multisite, would have saved a lot of back and forth with tech staff.  At the very least, create some sort of API or secure service for automating the firewall exceptions in cases like this where sites are being created dynamically via an application like WordPress multisite.

Anyways, again we do like what we have so far with FireHost except for this firewall experience, but I just wanted to post this up in case anyone else is thinking of using multi-site with FireHost and wondering why its not working as expected.

(I also have another reason for posting…secretly hoping some server guru out there will be able to explain how I’m either an idiot for expecting the firewall to be set up so multi-site works, or how FireHost can do things so multisite will work fine).

Update: December 2014

Just posting an update for anyone who visits this post via search engines (there are a few of you its seems.  Besides the fact that Firehost did contact us within the 30 day window that was put forward by their CEO/founder in the comments to this post, we never did get on any “beta” program.  The solution they are proposing for clients needing their own managed WAF is a $1500+/mo cost solution, which is not startup friendly.  They did offer to help get us up on ModSecurity as an alternative to their WAF solution but in the end my client and I just decided to fire Firehost (yeah pun intended) as our hosting provider and we joined the cool cats over at Digital Ocean.  We figured if we’re going to be doing most of the server setup/managing ourselves then no sense in paying a “managed” hosting provider to do it for us.  We’ve been on Digital Ocean for about 5 months now and absolutely LOVE it.

Get wp-cli running with MAMP

I got really intrigued with the wp-cli tool for command line WordPress (seriously awesome, check it out)… however I haven’t switched my osx machine to use the built in php and mysql so I kept getting this error:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

Easy fix:

sudo ln -s /Applications/MAMP/tmp/mysql/mysql.sock /tmp/mysql.sock

And BOOM! I’ve got wp-cli working now.

announcements are fun…

I always like hearing from people about new things happening in their lives and I’m excited to share something new happening in mine.  For most of the last year as a part of my web development business I’ve been helping a company called Event Espresso with their website redesign and some bits and pieces that are running their automatic plugin updates and membership system.  It was a really fun project to work on and we just launched their website two weeks ago.

In the process, I was working closely with the Event Espresso team and we realized that we worked well together.  I was invited to join their team as their core developer and I’ve accepted.

I’m REALLY excited about this transition because I get to keep building cool stuff and I really see Event Espresso becoming THE “go to” plugin for events and ticket sales and I’m looking forward to being a part of and contributing to their growth.

Are you running events?  Are you looking for an easy way to manage registrations and ticket sales?  You really need to check them out.

Oh and you know what else is cool?  My family and I don’t have to move.  EE’s team is a distributed team working from locations all over.  Fun stuff!

Early Adopter Disease

Here’s some symptoms of early adopter’s disease…

  • you subscribe to multiple tech blog feeds.  Even worse, you follow all their twitter feeds.  Even worse than that, you are a fan of their facebook page. Oh and  by “multiple”, I mean so many that you have 100 new posts an hour coming into your feed reader.
  • All non essential activity stops and you drool when watching an announcement on the latest gadget.  Essential activity is breathing.
  • When a new gadget is released you read every article that get’s written on it.  Anything else is old news.  What oil spill?
  • You know of gadgets that no one else you know has heard of.
  • You begin a lot of conversations with, “Hey did you hear about this? ___________”  and proceed to describe in glorious detail the incredible features of the gadget that will save the world.  Well, at least make the world a better place to live.  Well, at least make YOUR world a better place to live….
  • Things aren’t cool, they are magical.
  • You don’t understand why people get frustrated when something doesn’t work.  Your patience level is god-like when working with new stuff.  Which is probably why….
  • …waiting in lines is your favorite sport.
  • You sign up for all the beta stuff.  Even worse you sign up for alpha stuff.
  • You know what that means.
  • Every outlet in your house is taken up by the power bricks of your stuff.
  • When you are without internet access, you’re paralyzed and cannot do anything.  (This is actually a symptom for a few other diseases too – like internet occuporitus syndrome)
  • You see nothing wrong with people who buy tickets for this (and would be one of the first ones to do it if it wasn’t for all the money you spend on other stuff, like new gadgets and things…)

Do you have the disease?  Is there anything else you would add to the list?

How is Mobile Technology Changing the church and the Non-Profit Sector?

The title of this post is taken from a question posed over at ChurchDrop a while ago (along with an iPhone giveaway – great way of generating comments on the subject!) and I left a comment there so good I thought I’d repost it here [tongue in cheek] and add to it.  Actually, there’s a lot of great comments over at the original post, feel free to add to the conversation there, or here!  So here’s what I wrote:

Mobile technology is changing the church and non-profit sector in the following ways:

1. Real-time communication

People react quicker to what they are experiencing. (especially applicable to multi-site discussions -> see tony morgan’s observations) This also applies to the reporting of people on the field (i.e. missionaries, short term missions trips etc). It can be easier to communicate via mobile than any other technology from a user standpoint and this means that the message gets out and is widely distributed in a matter of seconds rather than minutes or days.  Sometimes whether we want it to or not.

Therefore, the real-time nature of mobile communication means that there must be an even greater awareness on the part of the church or non-profit to what message they are communicating in everything they say and do (even the unintended messages must be considered).  It has become (or will become depending on your context) increasingly difficult to change your message or modify it before it goes “public” because mobile makes everything public.

2. HOW things get communicated.

With the increasing penetration of mobile devices consideration needs to be given to how churches and non-profits communicate in ways that fully capitalize on the way people use those devices. In my opinion,  any church/non-profit that DOESN’T take this into consideration is at risk of losing a valuable avenue of communication for their constituency (granted this does depend on the particular penetration of mobile technology in the reach of the organization).  For much of the non-western world mobile IS the way to communicate.  North America is starting to catch up and the advent of smartphones means that more and more people are connected to the online grid 24/7.  Which brings me to the next point…

3. Social Media use.

In my eyes you can’t talk about mobile technology without including social media in the discussion (I think the rise of social media and mobile are connected).  It is really social media that has made mobile go from a tool used to connect with a few trusted friends and family to being a portal to online community.  The question then is what is the church or non-profit doing to connect and engage with this online-community?

4. Giving

This has only in the past year started to gain traction but the Haiti earthquake has a lot to teach on the ability of mobile technology to facilitate a quick and legitimate way of raising funds.  Check out a few articles on this:

The day is already here where churches can make it possible for congregation members to give via mobile during the service.  There are already companies offering that ability to churches (unfortunately I haven’t been able to find any that offer this service in Canada – we always seem to be a bit behind here up North).  I suspect that this will be one of the major ways in which mobile technology affects the church an non-profit sector.

So what do you think?  Are there any other ways you think we will see the church and non-profits affected by this tech?  Feel free to post your thoughts below.

Desktop Application from Web URL’s via Google Chrome

I came across this article on Church Crunch and thought, “this is something I’m already doing!” – but then I realized after watching the video there that I’m actually doing it differently than they suggested.  So I just thought I’d throw up this short screencast on how to create a desktop application using Google Chrome.  Just for you:

The Nightmare of a Techie…

I had a scary thought this morning.  For all of us techie, early adopter, and internet civvies –  what would we do if somehow we were lifted out of our life in modern society and transplanted 100 years ago (circa 1908)?  Doesn’t that make you shudder?

Here’s what I’d be doing…

earlyflightattempts

[photo from public.resource.org]

New Website Release: VOHMalawi.org

Village of Hope Malawi Website ScreenshotA couple of days ago I released a new website that I designed for some friends of mine who are the new directors of the Village of Hope in Malawi, Africa.  David and Connie Buzikievich are an awesome couple who are following through with their offer to God to take them wherever He wants and use them however He wants.  Their example of selfless service is inspiring!

Ever since my short term missions trip to the Village of Hope in Zimbabwe my heart has been captured by the work the Village’s of Hope are doing in Africa to make a difference in children’s lives (and indeed in the communities where the Villages of Hope are planted!).  One of the exciting ways I’ve been able to contribute to the work they are doing is by hosting and designing websites for both the Village of Hope, Zimbabwe, and now the Village of Hope, Malawi.  Getting the word out and keeping supporters informed is always one of the bigger challenges facing those working at the Villages and doing this helps free time and resources for focusing on what they need to do.

You can read all about the awesome things happening at the Villages of Hope in Zimbabwe and Malawi by going to vohmalawi.org and vohzimbabwe.com.

Village of Hope Zimbabwe Website ScreenshotOh, and by the way,  one of the reasons I’ve posted this is to see if there are any other web developers, designers, or hosting gurus who want to help out too!  If so, get in touch with me and I’ll see what I can do to hook you up.  Better yet, get in touch with a missionary or missions organization you know of and volunteer your services to them.  Using your gifts in this way is an awesome way to give towards the cause and help make a difference!