#include <cstdio>
#include <iostream>
#include <vector>
#include <list>
#include <cmath>
#include <fstream>
#include <algorithm>
#include <string>
#include <queue>
#include <set>
#include <map>
#include <complex>
#include <iterator>
#include <cstdlib>
#include <cstring>
#include <sstream>

using namespace std;

#define EPS (1e-10)
#define EQ(a,b) (abs((a) - (b)) < EPS)
#define EQV(a,b) (EQ((a).real(),(b).real()) && EQ((a).imag(),(b).imag()))

typedef complex<double> P;
typedef pair<P,P> Edge;
typedef long long ll;

const double PI=4*atan(1.0);
const int MAX_SIZE = 10000;

// àÏ
double dot(P a, P b) {
  return (a.real() * b.real() + a.imag() * b.imag());
}
// OÏ
double cross(P a, P b) {
  return (a.real() * b.imag() - a.imag() * b.real());
}
// a1,a2ð[_Æ·éüªÆb1,b2ð[_Æ·éüªÌð·»è
int is_intersected_ls(P a1, P a2, P b1, P b2) {
    // üªª½sÈêÍdÈÁÄ¢È¢±ÆÉ·é
    if(abs(cross(a2-a1,b2-b1)) < EPS){
        return 0;
    }
    return ( cross(a2-a1, b1-a1) * cross(a2-a1, b2-a1) < EPS ) &&
        ( cross(b2-b1, a1-b1) * cross(b2-b1, a2-b1) < EPS );
}
// _ª½p`ÌÉ¶Ý·é©Ç¤©
bool isInPolygon(vector<P> &points,P s){
	if(points.size()==1)return false;
	vector<P> v;
	for(int i=0;i<(int)points.size();i++)v.push_back(points[i]-s);
	int sign=0;
	for(int i=1;i<=(int)v.size();i++){
		int prv=(i-1+v.size())%v.size();
		int cur=i%v.size();
		double c=cross(v[prv],v[cur]);
		if(EQ(c,0))continue;
		else if(sign==0){
			if(c>0)sign=1;
			else sign=-1;
		}
		else{
			if(sign==-1&&c>0)return false;
			else if(sign==1&&c<0)return false;
		}
	}
	return true;
}

struct Rec{
	vector<P> p;
};

class UnionFindTree{
private:
  // indexÌÌem[h
  // indexðªÆ·éØÌN
  // ØÌÅål
  int treeSize;
public:
  vector<int> par;
  vector<int> rank;
  UnionFindTree(int initTreeSize = 1000){
	  // øÅ^¦çê½lðÅåi[Æ·éUnionFindTreeÌì¬
	  treeSize = initTreeSize;
	  init();
  }
  void init(){
	  for(int i = 0; i < treeSize; i++){
		  par.push_back(i);
		  rank.push_back(0);
	  }
  }
  // ^¦çê½ªi[³êÄ¢éØÌ[gðTõ
  int find(int x){
	  if(par[x] == x)return x;
	  else return par[x] = find(par[x]);
  }
  void unite(int x,int y){
	  x = find(x);
	  y = find(y);
	  if(x == y)return;
	  if(rank[x] < rank[y])par[x] = y;
	  else{
		  par[y] = x;
		  if(rank[y] == rank[x])rank[x]++;
	  }
  }
  bool same(int x,int y){
	  return find(x) == find(y);
  }
};

// aÆbÌé`ªÚµÄ¢é©Ç¤©
bool isTouchRectangle(Rec &a,Rec &b){
	// aÌe¸_ªbÌÉ¶ÝµÄ¢é©`FbN
	for(int i=0;i<4;i++)if(isInPolygon(b.p,a.p[i]))return true;
	// t
	for(int i=0;i<4;i++)if(isInPolygon(a.p,b.p[i]))return true;
	// eÓª¼ûÌÇê©ÌÓÆÚµÄ¢é©
	for(int i=0;i<4;i++){
		int curi=i;
		int prvi=(i-1+4)%4;
		for(int j=0;j<4;j++){
			int curj=j;
			int prvj=(j-1+4)%4;
			if(is_intersected_ls(a.p[curi],a.p[prvi],b.p[curj],b.p[prvj]))return true;
		}
	}
	return false;
}

Rec recs[1001];
int main(){
	int t;
	while(cin>>t&&t!=0){
		while(t--){
			int n;
			cin>>n;
			for(int i=0;i<n;i++){
				recs[i].p.clear();
				for(int j=0;j<4;j++){
					int x,y;
					cin>>x>>y;
					recs[i].p.push_back(P(x,y));
				}
			}
			UnionFindTree uft(n);
			for(int i=0;i<n;i++)for(int j=i+1;j<n;j++)
				if(isTouchRectangle(recs[i],recs[j]))uft.unite(i,j);
			set<int> s;
			for(int i=0;i<n;i++)s.insert(uft.find(i));
			cout<<s.size()<<endl;
		}
	}
	return 0;
}

Measured memory_bytes: 936